Выполняет ли листинг `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".)
Ответы
Ответ 1
У людей часто возникает впечатление, что операции с плавающей запятой дают результаты с небольшими непредсказуемыми, квази-случайными ошибками. Это впечатление неверно.
Вычисления с плавающей точкой максимально точны. 18/3
всегда будет давать ровно 6. Результат 1/3
не будет ровно на одну треть, но это будет самое близкое число к одной трети, которое будет представлено как число с плавающей запятой.
Таким образом, показанные вами примеры гарантированно будут всегда работать. Что касается вашего предлагаемого "гарантированного пола/потолка", это не очень хорошая идея. Некоторые последовательности операций могут легко вывести ошибку намного выше 1e-10
, а для некоторых других случаев использования 1e-10
будет корректно распознаваться (и ceil'ed) как отличное от нуля.
Как правило, жестко закодированные значения epsilon являются ошибками в вашем коде.
Ответ 2
В конкретных примерах, которые вы перечисляете, я не думаю, что эти ошибки когда-либо произойдут.
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
Сказав это, если у вас есть математика, которая зависит от этих функций, которые ведут себя корректно для чисел с плавающей запятой, это может осветить конструктивный недостаток, который вам нужно пересмотреть/пересмотреть.
Ответ 3
Такие результаты могут появиться при работе с удвоениями. Вы можете использовать раунд, или вы можете вычесть 0.5, затем использовать функцию std:: ceil.