TL;DR: дело не в том, что тип uint64_t
не может правильно отображать pow(2,64)-1
, а наоборот: double
не может хранить ровно 264 - 1 из-за отсутствия значащих бит. Вы можете сделать это только с типами с точностью 64 бита или более (например, long double
на многих платформах). Попробуйте std::pow(2.0L, 64) - 1.0L
(обратите внимание на суффикс L
) или powl(2.0L, 64) - 1.0L;
и посмотрите
В любом случае вы не должны использовать тип с плавающей запятой для целочисленной математики с самого начала. Мало того, что вычисление pow(2, x)
намного медленнее, чем 1ULL << x
, это также вызовет проблему, которую вы видели, из-за ограниченной точности double
. Вместо этого используйте uint64_t max2 = -1
или ((unsigned __int128)1ULL << 64) - 1
, если компилятор поддерживает __int128
.
pow(2, 64) - 1
— это выражение double
, не int
, поскольку pow
не имеет перегрузки, которая возвращает целочисленный тип. Целое число 1
будет повышено до того же ранга, что и результат pow
.
Однако, поскольку длина двойной точности IEEE-754 составляет всего 64 бита, вы никогда не можете хранить значения, имеющие 64 или более значащих бита, например 264-1.
Таким образом, pow(2, 64) - 1
будет округлено до ближайшего представимого значения, которое равно самому pow(2, 64)
, а pow(2, 64) - 1 == pow(2, 64)
даст 1. Наибольшее значение, которое меньше этого, равно 18446744073709549568 = 264 - 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
Вы можете ясно видеть, что long double
может хранить правильное значение, как и ожидалось.
На многих других платформах double
может быть четырехкратной точности IEEE-754 или двойной-двойной. Оба имеют более 64 битов значимости, поэтому вы можете делать одно и то же. Но, конечно, накладные расходы будут выше
person
phuclv
schedule
01.03.2019
2^64-1
в двойном размере, но на самом деле страдаете от округления. - person Aconcagua   schedule 01.03.2019double
нет, но вы не проверяете неправильные цифры - person Caleth   schedule 01.03.2019#include <cmath>
и (возможно)std::
с сайта вызова - person Caleth   schedule 01.03.2019foo(...)
. Я просто показывал, что указанный набор перегрузки не может скомпилироваться с данным выражением. Хотя, наверное, было бы разумнее сделать это именно так. - person Justin   schedule 01.03.2019uint64_t max2b = pow(2.0, 64.0) - 2048.0;
будет ближе к тому, что вы хотите, поскольку 2048.0 - это эпсилон в этом диапазоне. (Предполагая IEEE 754.) - person Eljay   schedule 01.03.20192^64-1 == uint64_t(-1)
. - person Max Langhof   schedule 01.03.2019