Бросок из беззнакового длинного длинного в двойное и наоборот изменяет значение
При написании кода на С++ я внезапно осознал, что мои номера неправильно выбраны из 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 третье напечатанное значение равно нулю