static_cast
переход от числа с плавающей точкой к целому числу просто удаляет дробную точку числа. Например static_cast<int>(13.9999999)
доходность 13
,
Не все целые числа представляются в виде чисел с плавающей запятой. Например, внутренне ближе всего float
до 13 000 000 может быть: 12999999.999999
,
В этом гипотетическом случае я ожидаю получить неожиданный результат от:
const auto foo = 12'999'999.5F;
const auto bar = static_cast<long long>(ceil(foo));
Я предполагаю, что такой сбой действительно произойдет в какой-то момент, если не обязательно в 13 000 000. Я просто хотел бы знать диапазон, в котором я могу доверять static_cast<long long>(ceif(foo))
?
Например, внутренним ближайшим к 13 000 000 может быть: 12999999,9999999.
Это невозможно в любом обычном формате с плавающей точкой. Представление чисел с плавающей точкой эквивалентно M•бе, где б является фиксированной базой (например, 2 для двоичной с плавающей точкой) и M а также е являются целыми числами с некоторыми ограничениями на их значения. Для того, чтобы за 13 000 000Икс быть представленным, где Икс некоторое положительное значение меньше 1, е должен быть отрицательным (потому что M•бе для неотрицательных е является целым числом). Если так, то M• б0 целое число больше, чем M•бе, таким образом, это больше чем 13 000 000, и поэтому 13 000 000 могут быть представлены как M»•б0, где M» положительное целое число меньше M и, следовательно, вписывается в диапазон допустимых значений для M (в любом обычном формате с плавающей точкой). (Возможно, какой-то странный формат с плавающей точкой может наложить странный диапазон на M или же е это мешает, но ни один нормальный формат не делает.)
Что касается вашего кода:
auto test = 0LL;
const auto floater = 0.5F;
for(auto i = 0LL; i == test; i = std::ceil(i + floater)) ++test;
cout << test << endl;
когда i
8,388,608, математический результат 8,388,608 + 0,5 — 8,388,608,5. Это не представимо в float
формат в вашей системе, поэтому он был округлен до 8,388,608. ceil
из этого 8 388 608. С этой точки зрения, test
было 8,388,609, поэтому цикл остановился. Таким образом, этот код не демонстрирует, что 8 388 608,5 представимы, а 8 388 609 нет.
Поведение, похоже, вернется к норме, если я это сделаю: ceil (8’388’609.5F), который вернёт 8,388,610 правильно.
8 388 609,5 не представляются в float
формат в вашей системе, поэтому он был округлен по правилу «округлить до ближайшего, привязать к четному». Два ближайших представимых значения — 8 388 609 и 8 388 610. Поскольку они одинаково далеко друг от друга, результат составил 8 388 610. Это значение было передано ceil
, который, конечно, вернул 8 388 610.
На Visual Studio 2015 я получил 8 388 609, что является ужасающим небольшим безопасным диапазоном.
В базовом 32-разрядном двоичном формате IEEE-754 все целые числа от -16 777 216 до + 16 777 216 представимы, поскольку формат имеет 24-разрядное значение.
Числа с плавающей точкой представлены 3 целые, центибарQ где:
Из этого легко видеть, что с плавающей точкой с возможностью представления 12,999,999.999999 также имеет возможность представления 13,000,000.000000 используя с из 1.300.000.000.000 и Q из -5.
Этот пример немного надуман в том, что выбранный б является 10, где почти во всех реализациях выбранная база 2. Но стоит отметить, что даже с б из 2 Q функционирует как сдвиг влево или вправо от мантиссы.
Далее давайте поговорим о диапазоне здесь. Очевидно, что 32-разрядное число с плавающей запятой не может представлять все целые числа, представленные 32-разрядным целым числом, так как число с плавающей запятой также должно представлять столько много больших или меньших чисел. Поскольку показатель степени просто сдвигает мантиссу, число с плавающей запятой всегда может именно так представляют каждое целое число, которое может быть представлено его мантиссой. Учитывая традиционные двоичные базовые числа с плавающей запятой IEEE-754:
float
) имеет 24-битную мантиссу, поэтому она может представлять все целые числа в диапазоне [-16777215, 16777215]double
) имеет 53-битную мантиссу, поэтому она может представлять все целые числа в диапазоне [-9.007.199.254.740.991, 9.007.199.254.740.991]long double
в зависимости от реализации) имеет 113-битную мантиссу, поэтому может представлять все целые числа в диапазоне [-103,845,937,170,696,552,570,609,926,584,40,191, 103,845,937,170,696,552,570,609,926,584,40,191]C ++ обеспечивает digits
как метод нахождения этого числа для данного типа с плавающей запятой. (Хотя по общему признанию даже long long
слишком мал для представления 113-битной мантиссы.) Например, float
Максимальная мантисса может быть найдена:
(1LL << numeric_limits<float>::digits) - 1LL
Тщательно объяснив мантиссу, давайте вернемся к разделу экспоненты, чтобы поговорить о том, как с плавающей запятой на самом деле хранится. принимать 13,000,000.0 это может быть представлено как:
И так далее. Для традиционного двоичного формата IEEE-754 требуется:
Представление делается уникальным путем выбора наименьшего представимого показателя, который сохраняет старший значащий бит (MSB) в пределах выбранного размера и формата слова. Кроме того, показатель степени не представлен напрямую, но смещение добавляется так, что наименьший представимый показатель представляется как 1, причем 0 используется для субнормальных чисел
Чтобы объяснить это в более знакомой базе-10, если наша мантисса имеет 14 десятичные разряды, реализация будет выглядеть так:
В переводе на базу-2 это означает, что Q из numeric_limits<T>::digits - 1
имеет только нули после запятой. ceil
действует только при наличии дробной части числа.
Последняя точка объяснения здесь, это диапазон, в котором ceil
будет иметь эффект. После того, как показатель с плавающей запятой больше, чем numeric_limits<T>::digits
продолжая увеличивать это только вводит конечные нули в результирующее число, таким образом вызывая ceil
когда Q Больше или равно numeric_limits<T>::digits - 2LL
, И так как мы знаем MSB с будет использоваться в числе, это означает, что с должен быть меньше чем (1LL << numeric_limits<T>::digits - 1LL) - 1LL
Таким образом, для ceil
чтобы иметь влияние на традиционную двоичную IEEE-754 с плавающей точкой:
float
) должен быть меньше чем 8388607double
) должен быть меньше чем 4.503.599.627.370.495long double
в зависимости от реализации) должен быть меньше, чем 5.192.296.858.534.827.628.530.496.329.220.095