Гарантируется ли сохранение числа с плавающей точкой при переносе через double в C / C ++?

Если предположить, IEEE-754 Соответствие, гарантируется ли сохранение поплавка при транспортировке через double?

Другими словами, всегда ли будет выполнено следующее утверждение?

int main()
{
float f = some_random_float();
assert(f == (float)(double)f);
}

Предположим, что f может получить любое из специальных значений, определенных IEEE, таких как NaN и Infinity.

Согласно IEEE, есть ли случай, когда утверждение будет выполнено, но точное представление на уровне битов не сохраняется после транспортировки через double?

Фрагмент кода действителен как на C, так и на C ++.

34

Решение

Вам даже не нужно предполагать IEEE. C89 говорит в 3.1.2.5:

Набор значений типа float является подмножеством набора значений
типа double

И любой другой стандарт C и C ++ говорит эквивалентные вещи. Насколько я знаю, NaNs и бесконечности являются «значениями типа floatmsgstr «, хотя значения с некоторыми правилами особого случая используются в качестве операндов.

Тот факт, что преобразование float -> double -> float восстанавливает первоначальное значение float следует (в общем) из того факта, что все числовые преобразования сохраняют значение, если оно представимо в целевом типе.

Представления на уровне битов — это немного другой вопрос. Представьте, что есть значение float у этого есть два отличных побитовых представления. Тогда ничто в стандарте C не препятствует преобразованию float -> double -> float от одного к другому. В IEEE этого не произойдет для «фактических значений», если только нет битов заполнения, но я не знаю, исключает ли IEEE один NaN, имеющий различные побитовые представления. В любом случае, NaN не сравниваются с равными себе, поэтому также не существует стандартного способа определить, являются ли два NaN «одним и тем же NaN» или «разными NaN», кроме как их преобразование в строки. Проблема может быть спорной.

Следует обратить внимание на несоответствующие режимы компиляторов, в которых они хранят сверхточные значения «под прикрытием», например, промежуточные результаты, оставленные в регистрах с плавающей запятой и повторно используемые без округления. Я не думаю, что это приведет к сбою вашего примера кода, но как только вы делаете с плавающей точкой == это та вещь, о которой ты начинаешь беспокоиться.

30

Другие решения

С 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.

Таким образом, есть положение для таких специальных значений, и преобразования могут просто работать для них (в том числе для минус бесконечности и отрицательного нуля).

16

Утверждение не будет выполнено в режиме сброса в ноль и / или денормализованного в нуле режима (например, код, скомпилированный с -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);
}
2
По вопросам рекламы [email protected]