Почему uint64_t не может правильно показывать pow (2, 64) - 1? - PullRequest
0 голосов
/ 01 марта 2019

Я пытаюсь понять, почему uint64_t type не может правильно отобразить pow(2,64)-1.Стандарт cplusplus - 199711L.

Я проверил функцию pow() по стандарту C ++ 98, которая

double pow (double base     , double exponent);
float pow (float base      , float exponent);
long double pow (long double base, long double exponent);
double pow (double base     , int exponent);
long double pow (long double base, int exponent);

Итак, я написал следующий фрагмент

double max1 = (pow(2, 64) - 1);
cout << max1 << endl;

uint64_t max2 = (pow(2, 64) - 1);
cout << max2 << endl;

uint64_t max3 = -1;
cout << max3 << endl;

Выходы:

max1: 1.84467e+019
max2: 9223372036854775808
max3: 18446744073709551615

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

pow(2, 64) - 1 - это выражение double , , а не int, поскольку pow не имеет перегрузки, которая возвращает целое числотип.Литерал 1 будет повышен до того же ранга, что и результат pow

Однако, поскольку двойная точность IEEE-754 имеет длину только 64 бита, вы никогда не сможете хранить значения, имеющие 64 значащих бита или более, например2 64 -1

Так что pow(2, 64) - 1 будет округлено до ближайшего представимого значения , которое само по себе pow(2, 64), и pow(2, 64) - 1 == pow(2, 64) приведет к 1Наибольшее значение меньше 18446744073709549568 = 2 64 - 2048. Вы можете проверить это с помощью std::nextafter

На некоторых платформах (особенно x86, кромев MSVC) long double имеет 64 бита значения и , поэтому в этом случае вы получите правильное значение. следующий фрагмент

double max1 = pow(2, 64) - 1;
std::cout << "pow(2, 64) - 1 = " << std::fixed << max1 << '\n';
std::cout << "Previous representable value: " << std::nextafter(max1, 0) << '\n';
std::cout << (pow(2, 64) - 1 == pow(2, 64)) << '\n';

long double max2 = pow(2.0L, 64) - 1.0L;
std::cout << std::fixed << max2 << '\n';

распечатывает

pow(2, 64) - 1 = 18446744073709551616.000000
Previous representable value: 18446744073709549568.000000
1
18446744073709551615.000000

На многих других платформах double может иметь IEEE-754 с четверной точностью или дабл-дабл .Оба имеют более 64 битов значений, поэтому вы можете делать то же самое.Но, конечно, издержки будут выше

В любом случае, вы не должны использовать тип с плавающей точкой для целочисленной математики с самого начала.Мало того, что вычисление pow(2, x) намного медленнее, чем 1ULL << x, это также вызовет возникшую проблему из-за ограниченной точности double.Вместо этого используйте uint64_t max2 = -1 или ((unsigned __int128)1ULL << 64) - 1, если компилятор поддерживает этот тип

0 голосов
/ 01 марта 2019

Числа с плавающей запятой имеют конечную точность.

В вашей системе (и, как правило, предполагая двоичный формат IEEE-754) 18446744073709551615 не является числом, имеющим представление в формате double.Самое близкое число, которое имеет представление, оказывается 18446744073709551616.

. Вычитание (и сложение) двух чисел с плавающей запятой дико разных величин обычно приводит к ошибке.Эта ошибка может быть существенной по отношению к меньшему операнду.В случае 18446744073709551616. - 1. -> 18446744073709551616. ошибка вычитания равна 1, что фактически совпадает со значением меньшего операнда.

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

...