Всегда ли приведение 'std:: floor ()' и 'std:: ceil ()' к целочисленному типу дает правильный результат?
Я параноик, что одна из этих функций может дать неправильный результат, как это:
std::floor(2000.0 / 1000.0) --> std::floor(1.999999999999) --> 1
or
std::ceil(18 / 3) --> std::ceil(6.000000000001) --> 7
Может ли произойти нечто подобное? Если действительно существует такой риск, я планирую использовать функции ниже, чтобы работать безопасно. Но так ли уж это необходимо?
constexpr long double EPSILON = 1e-10;
intmax_t GuaranteedFloor(const long double & Number)
{
if (Number > 0)
{
return static_cast<intmax_t>(std::floor(Number) + EPSILON);
}
else
{
return static_cast<intmax_t>(std::floor(Number) - EPSILON);
}
}
intmax_t GuaranteedCeil(const long double & Number)
{
if (Number > 0)
{
return static_cast<intmax_t>(std::ceil(Number) + EPSILON);
}
else
{
return static_cast<intmax_t>(std::ceil(Number) - EPSILON);
}
}
(примечание: Я предполагаю, что данный аргумент 'long double' будет соответствовать возвращаемому типу 'intmax_t'.)
4 ответа:
У людей часто складывается впечатление, что операции с плавающей запятой дают результаты с небольшими, непредсказуемыми, квази-случайными ошибками. Это впечатление неверно.
Вычисления с плавающей точкой являютсянастолько точными, насколько это возможно .
18/3
всегда будет производить ровно 6 . Результат1/3
не будет точно одной третью, но это будет самое близкое число к одной трети, которое можно представить как число с плавающей точкой.Таким образом, примеры, которые вы показали, гарантированно всегда работать. Что касается предложенного вами "гарантированного пола/ceil", это не очень хорошая идея. Некоторые последовательности операций могут легко взорвать ошибку намного выше
1e-10
, а некоторые другие случаи использования потребуют, чтобы1e-10
был правильно распознан (и ceiled) как ненулевой.Как правило, жестко закодированные значения Эпсилона являются ошибками в вашем коде.
В конкретных примерах, которые вы перечисляете, я не думаю, что эти ошибки когда-либо возникнут.
std::floor(2000.0 /*Exactly Representable in 32-bit or 64-bit Floating Point Numbers*/ / 1000.0 /*Also exactly representable*/) --> std::floor(2.0 /*Exactly Representable*/) --> 2 std::ceil(18 / 3 /*both treated as ints, might not even compile if ceil isn't properly overloaded....?*/) --> 6 std::ceil(18.0 /*Exactly Representable*/ / 3.0 /*Exactly Representable*/) --> 6
Сказав, что, если у вас есть математика, которая зависит от этих функций, ведущих себя точно правильно для чисел с плавающей запятой, это может осветить недостаток дизайна, который вам нужно пересмотреть/пересмотреть.
Пока значения с плавающей запятой x и y точно представляют целые числа в пределах типа, который вы используете, нет никаких проблем -
Однако, как только значения с плавающей запятой выходят за пределы целочисленного представимого диапазона для типа (, представляющего целые числа в двойниках ), эпсилоны не помогают.x / y
всегда будет давать значение с плавающей запятой, которое точно представляет целочисленный результат. приведение к int, как вы делаете, всегда будет работать.Рассмотрим это образец. 16777217-наименьшее целое число, которое не может быть точно представлено в виде 32-разрядного
float
:В этом случае ошибка отрицательна; в других случаях (попробуйте 16777219 / 1549) ошибка положительна.int ix=16777217, iy=97; printf("%d / %d = %d", ix, iy, ix/iy); // yields "16777217 / 97 = 172961" which is accurate float x=ix, y=iy; printf("%f / %f = %f", x, y, x/y); // yields "16777216.000000 / 97.000000 = 172960.989691"
Хотя и заманчиво добавить Эпсилон, чтобы заставить
Если это вступает в игру, вам придется рассмотреть возможность изменения вашего математического подхода-порядка операций, работы с логарифмами и т. д.floor
работать, это не сильно увеличит точность. Когда значения отличаются более чем на порядки, ошибка становится больше 1 и целочисленная точность не может быть гарантирована. в частности, когдаx/y
превышает макс. представимо, ошибка может превышать 1,0, так что Эпсилон не поможет.