Выполняет ли листинг `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.