Бросок из беззнакового длинного длинного в двойное и наоборот изменяет значение

При написании кода на С++ я внезапно осознал, что мои номера неправильно выбраны из double в unsigned long long.

Чтобы быть конкретным, я использую следующий код:

#define _CRT_SECURE_NO_WARNINGS

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

int main()
{
  unsigned long long ull = numeric_limits<unsigned long long>::max();
  double d = static_cast<double>(ull);
  unsigned long long ull2 = static_cast<unsigned long long>(d);
  cout << ull << endl << d << endl << ull2 << endl;
  return 0;
}

Идеальный живой пример.

Когда этот код выполняется на моем компьютере, у меня есть следующий вывод:

18446744073709551615
1.84467e+019
9223372036854775808
Press any key to continue . . .

Я ожидал, что первое и третье числа будут точно такими же (как и на Ideone), потому что я был уверен, что long double взял 10 байт и сохранил мантиссу в 8 из них. Я бы понял, если третье число было усечено по сравнению с первым - просто для случая я ошибаюсь в формате чисел с плавающей запятой. Но здесь значения в два раза отличаются!

Итак, главный вопрос: почему? И как я могу предсказать такие ситуации?

Некоторые детали: я использую Visual Studio 2013 в Windows 7, компилирую для x86 и sizeof(long double) == 8 для своей системы.

Ответ 1

18446744073709551615 не точно представлен в double (в IEEE754). Это не неожиданно, так как 64-битная плавающая точка, очевидно, не может представлять все целые числа, которые представлены в 64 битах.

В соответствии со стандартом С++ он определяется реализацией, используется ли значение next-high или next-lower double. По-видимому, в вашей системе он выбирает следующее самое высокое значение, которое кажется 1.8446744073709552e19. Вы можете подтвердить это, выведя double с большим количеством цифр.

Обратите внимание, что это больше, чем исходный номер.

Когда вы преобразовываете этот double в integer, поведение распространяется на [conv.fpint]/1:

Значение значения типа с плавающей запятой может быть преобразовано в prvalue целочисленного типа. Преобразование усекает; т.е. дробная часть отбрасывается. Поведение undefined, если усеченное значение не может быть представлено в целевом типе.

Таким образом, этот код потенциально вызывает поведение undefined. Когда поведение undefined произошло, все может произойти, включая (но не ограничиваясь этим) фиктивный вывод.


Вопрос был первоначально отправлен с long double, а не double. На моем gcc поведение long double ведет себя корректно, но в OP MSVC он дал ту же ошибку. Это можно объяснить gcc с использованием 80-битного long double, но MSVC с использованием 64-битного long double.

Ответ 2

Проблема удивительно проста. Это то, что происходит в вашем случае:

18446744073709551615 при преобразовании в double округляется вверх до ближайшего числа, которое может представлять собой плавающая точка. (Ближайшее представимое число больше).

Когда это преобразуется обратно в unsigned long long, оно больше, чем max(). Формально, преобразование этого обратно в unsigned long long составляет undefined, но то, что, по-видимому, происходит в вашем случае, - это обернуть.

Наблюдаемое значительно меньшее число является результатом этого.

Ответ 3

Это связано с приближением double к long long. Его точность означает ~ 100 единиц ошибки при 10 ^ 19; когда вы пытаетесь преобразовать значения вокруг верхнего предела длинного длинного диапазона, он переполняется. Попробуйте преобразовать более 10000 значений ниже:)

BTW, в Cygwin третье напечатанное значение равно нулю