Если предположить, IEEE-754 Соответствие, гарантируется ли сохранение поплавка при транспортировке через double?
Другими словами, всегда ли будет выполнено следующее утверждение?
int main()
{
float f = some_random_float();
assert(f == (float)(double)f);
}
Предположим, что f
может получить любое из специальных значений, определенных IEEE, таких как NaN и Infinity.
Согласно IEEE, есть ли случай, когда утверждение будет выполнено, но точное представление на уровне битов не сохраняется после транспортировки через double?
Фрагмент кода действителен как на C, так и на C ++.
Вам даже не нужно предполагать IEEE. C89 говорит в 3.1.2.5:
Набор значений типа
float
является подмножеством набора значений
типаdouble
И любой другой стандарт C и C ++ говорит эквивалентные вещи. Насколько я знаю, NaNs и бесконечности являются «значениями типа float
msgstr «, хотя значения с некоторыми правилами особого случая используются в качестве операндов.
Тот факт, что преобразование float -> double -> float восстанавливает первоначальное значение float
следует (в общем) из того факта, что все числовые преобразования сохраняют значение, если оно представимо в целевом типе.
Представления на уровне битов — это немного другой вопрос. Представьте, что есть значение float
у этого есть два отличных побитовых представления. Тогда ничто в стандарте C не препятствует преобразованию float -> double -> float от одного к другому. В IEEE этого не произойдет для «фактических значений», если только нет битов заполнения, но я не знаю, исключает ли IEEE один NaN, имеющий различные побитовые представления. В любом случае, NaN не сравниваются с равными себе, поэтому также не существует стандартного способа определить, являются ли два NaN «одним и тем же NaN» или «разными NaN», кроме как их преобразование в строки. Проблема может быть спорной.
Следует обратить внимание на несоответствующие режимы компиляторов, в которых они хранят сверхточные значения «под прикрытием», например, промежуточные результаты, оставленные в регистрах с плавающей запятой и повторно используемые без округления. Я не думаю, что это приведет к сбою вашего примера кода, но как только вы делаете с плавающей точкой ==
это та вещь, о которой ты начинаешь беспокоиться.
С C99:
6.3.1.5. Реальные плавающие типы
1 Когда float повышается до double или long double, или double повышается до long double, его значение не изменяется.
2 Когда двойник понижается до значения float, длинный двойник понижается до значения double или float, или значение, представленное с большей точностью и диапазоном, чем требуется его семантическим типом (см. 6.3.1.8), явно преобразуется в его семантический тип, если конвертируемое значение может быть представлено точно в новом типе, оно не изменяется …
Я думаю, это гарантирует вам, что преобразование float-> double-> float сохранит исходное значение float.
Стандарт также определяет макросы INFINITY
а также NAN
в 7.12 Mathematics <math.h>
:
4 Макрос INFINITY расширяется до константного выражения типа float, представляющего положительную или беззнаковую бесконечность, если доступно; иначе к положительной константе типа float, которая переполняется во время перевода.
5 Макрос NAN определяется тогда и только тогда, когда реализация поддерживает тихие NaN для типа с плавающей запятой. Он расширяется до константного выражения типа float, представляющего тихий NaN.
Таким образом, есть положение для таких специальных значений, и преобразования могут просто работать для них (в том числе для минус бесконечности и отрицательного нуля).
Утверждение не будет выполнено в режиме сброса в ноль и / или денормализованного в нуле режима (например, код, скомпилированный с -mfpmath = sse, -fast-math и т. Д., Но также и в кучах компиляторов и архитектур по умолчанию, таких как Intel Компилятор C ++), если f денормализован.
Вы не можете создать денормализованный float в этом режиме, но сценарий все еще возможен:
а) Денормализованный поплавок исходит из внешнего источника.
б) Некоторые библиотеки вмешиваются в режимы FPU, но забывают (или намеренно избегают) сбрасывать их после каждого вызова функции, что позволяет вызывающей стороне не соответствовать нормировке.
Практический пример, который печатает следующее:
f = 5.87747e-39
f2 = 5.87747e-39
f = 5.87747e-39
f2 = 0
error, f != f2!
Пример работает как для VC2010, так и для GCC 4.3, но предполагает, что VC использует SSE для математики по умолчанию, а GCC использует FPU для математики по умолчанию. В противном случае пример может не проиллюстрировать проблему.
#include <limits>
#include <iostream>
#include <cmath>
#ifdef _MSC_VER
#include <xmmintrin.h>
#endif
template <class T>bool normal(T t)
{
return (t != 0 || fabsf( t ) >= std::numeric_limits<T>::min());
}
void csr_flush_to_zero()
{
#ifdef _MSC_VER
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
#else
unsigned csr = __builtin_ia32_stmxcsr();
csr |= (1 << 15);
__builtin_ia32_ldmxcsr(csr);
#endif
}
void test_cast(float f)
{
std::cout << "f = " << f << "\n";
double d = double(f);
float f2 = float(d);
std::cout << "f2 = " << f2 << "\n";
if(f != f2)
std::cout << "error, f != f2!\n";
std::cout << "\n";
}
int main()
{
float f = std::numeric_limits<float>::min() / 2.0;
test_cast(f);
csr_flush_to_zero();
test_cast(f);
}