Когда static_casting Результат Ceil компрометирует результат?

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))?

0

Решение

Например, внутренним ближайшим к 13 000 000 может быть: 12999999,9999999.

Это невозможно в любом обычном формате с плавающей точкой. Представление чисел с плавающей точкой эквивалентно Mбе, где б является фиксированной базой (например, 2 для двоичной с плавающей точкой) и M а также е являются целыми числами с некоторыми ограничениями на их значения. Для того, чтобы за 13 000 000Икс быть представленным, где Икс некоторое положительное значение меньше 1, е должен быть отрицательным (потому что Mбе для неотрицательных е является целым числом). Если так, то M• б0 целое число больше, чем Mбе, таким образом, это больше чем 13 000 000, и поэтому 13 000 000 могут быть представлены как б0, где положительное целое число меньше 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-разрядное значение.

1

Другие решения

Числа с плавающей точкой представлены 3 целые, центибарQ где:

  • с это мантисса (так для числа: 12,999,999.999999 с было бы 12.999.999.999.999)
  • Q это показатель степени (так для числа: 12,999,999.999999 Q было бы -6)
  • б это база (IEEE-754 требует б быть либо 10 или же 2; в представлении выше б является 10)

Из этого легко видеть, что с плавающей точкой с возможностью представления 12,999,999.999999 также имеет возможность представления 13,000,000.000000 используя с из 1.300.000.000.000 и Q из -5.

Этот пример немного надуман в том, что выбранный б является 10, где почти во всех реализациях выбранная база 2. Но стоит отметить, что даже с б из 2 Q функционирует как сдвиг влево или вправо от мантиссы.


Далее давайте поговорим о диапазоне здесь. Очевидно, что 32-разрядное число с плавающей запятой не может представлять все целые числа, представленные 32-разрядным целым числом, так как число с плавающей запятой также должно представлять столько много больших или меньших чисел. Поскольку показатель степени просто сдвигает мантиссу, число с плавающей запятой всегда может именно так представляют каждое целое число, которое может быть представлено его мантиссой. Учитывая традиционные двоичные базовые числа с плавающей запятой IEEE-754:

  • 32-битный (float) имеет 24-битную мантиссу, поэтому она может представлять все целые числа в диапазоне [-16777215, 16777215]
  • 64-битный (double) имеет 53-битную мантиссу, поэтому она может представлять все целые числа в диапазоне [-9.007.199.254.740.991, 9.007.199.254.740.991]
  • 128-битный (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]
[источник]

обеспечивает digits как метод нахождения этого числа для данного типа с плавающей запятой. (Хотя по общему признанию даже long long слишком мал для представления 113-битной мантиссы.) Например, floatМаксимальная мантисса может быть найдена:

(1LL << numeric_limits<float>::digits) - 1LL

Тщательно объяснив мантиссу, давайте вернемся к разделу экспоненты, чтобы поговорить о том, как с плавающей запятой на самом деле хранится. принимать 13,000,000.0 это может быть представлено как:

  • с = 13, q = 6, б = 10
  • с = 130, q = 5, б = 10
  • с = 1300, q = 4, б = 10

И так далее. Для традиционного двоичного формата IEEE-754 требуется:

Представление делается уникальным путем выбора наименьшего представимого показателя, который сохраняет старший значащий бит (MSB) в пределах выбранного размера и формата слова. Кроме того, показатель степени не представлен напрямую, но смещение добавляется так, что наименьший представимый показатель представляется как 1, причем 0 используется для субнормальных чисел

Чтобы объяснить это в более знакомой базе-10, если наша мантисса имеет 14 десятичные разряды, реализация будет выглядеть так:

  • с = 13 000 000 000 000 поэтому MSB будет использоваться в представленном количестве
  • q = 6 Это немного сбивает с толку, это причина предвзятости, представленной здесь; логически q = -6 но смещение установлено так, что когда q = 0 только MSB с находится сразу слева от десятичной точки, а это означает, что с = 13 000 000 000 000, q = 0, б = 10 будет представлять 1,3
  • б = 10 опять же, вышеприведенные правила действительно требуются только для базы-2, но я показал их, как они будут применяться к базе-10 с целью объяснения

В переводе на базу-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 с плавающей точкой:

  • 32-битный (float) должен быть меньше чем 8388607
  • 64-битный (double) должен быть меньше чем 4.503.599.627.370.495
  • 128-битный (long double в зависимости от реализации) должен быть меньше, чем 5.192.296.858.534.827.628.530.496.329.220.095
0