2014-11-01 3 views
1

В настоящее время я пытаюсь подробно узнать о представлении с плавающей запятой, поэтому я немного поиграл. Делая это, я наткнулся на какое-то странное поведение; Я не могу понять, что происходит, и я был бы очень благодарен за понимание. Извиняюсь, если на это был дан ответ, мне было довольно сложно Google!Странное поведение при сравнении литого поплавка с нолем

#include <iostream> 
#include <cmath> 
using namespace std; 

int main(){ 

  float minVal = pow(2,-149); // set to smallest float possible 
   
  float nextCheck = static_cast<float>(minVal/2.0f); // divide by two 
  bool isZero = (static_cast<float>(minVal/2.0f) == 0.0f); // this evaluates to false 
  bool isZero2 = (nextCheck == 0.0f); // this evaluates to true 
  cout << nextCheck << " " << isZero << " " << isZero2 << endl; 
  // this outputs 0 0 1 
   
  return 0; 

} 

По сути то, что происходит это:

  • Я поставил MINVAL быть наименьшим поплавок, который может быть представлен с использованием одинарной точности
  • разделив на 2 должны давать 0 - мы на минимум
  • Действительно, isZero2 действительно возвращает true, но isZero возвращает false.

Что происходит - я бы подумал, что они идентичны? Компилятор пытается быть умным, говоря, что деление любого числа не может привести к нулю?

Благодарим за помощь!

+1

Как вы уверены, что 'pow (2, -149)' возвращает наименьший возможный поплавок? Существует множество проблем с расчетом расчета. – deepmax

+0

Кажется, что работает. Бинарное представление возвращает 1, что действительно является наименьшим возможным поплавком: Знак = 0; Exponent = 0; Mantissa = 1, так: 2^(- 23) * 2^(- 126) = 2^(- 149) – noctilux

+0

Получаю ваш ожидаемый результат на g ++ 4.8.2. '0 1 1'. –

ответ

5

Причина isZero и isZero2 могут оценить различные значения, и isZero может быть ложным, является то, что компилятор C++ разрешено осуществлять промежуточные операции с плавающей точкой с большей точностью, чем тип выражения будет указывать, но дополнительные точность присваивается при назначении.

Как правило, при генерации кода для 387 исторического FPU сгенерированные инструкции работают либо с 80-битным типом расширенной точности, либо, если FPU установлен на 53-битное значение (например, в Windows), странный тип с плавающей точкой с 53-битными знаками и 15-битными показателями.

В любом случае, minVal/2.0f оценивается точно потому, что диапазон экспонентов позволяет его представлять, но присвоение его nextCheck округляет его до нуля.

Если вы используете GCC, есть дополнительная проблема, что -fexcess-precision=standard еще не реализован для интерфейса C++, что означает, что код, сгенерированный g ++, не реализует точно то, что рекомендует стандарт.

+0

Большое спасибо, я чувствую, что я почти понимаю сейчас. Просто уточнить: в моем компиляторе Windows (GCC), -fexcess-precision = standard не был реализован, поэтому, хотя я выполняю роль float, он может использовать свое странное внутреннее 53-битное представление.С другой стороны, в g ++ в Ubuntu он выполняет бросок, а затем рассматривает его как float ожидаемым образом. Это правильно? – noctilux

+1

@noctilux Скорее всего, на Ubuntu он использует инструкции с плавающей запятой SSE2 и генерирует разделение с одной точностью для 'minVal/2.0f', которое дает ожидаемый результат с ожидаемой точностью. Вы можете решить вопрос, используя параметр '-S' для GCC, чтобы он генерировал код сборки в удобочитаемой форме. Инструкции, начинающиеся с 'f', являются историческими 387 инструкциями. 'divss' - это инструкция, которую вы увидите, с другой стороны, если компилятор генерирует код SSE2. –

+0

Паскаль, ты совершенно прав. Спасибо за очень проницательные ответы! – noctilux

Смежные вопросы