Почему numeric_limits Machine Epsilon не удовлетворяет условию 1 + e> 1?

Dec 24 2020

Если я не ошибаюсь, определение Machine Epsilon - это наименьшее число, удовлетворяющее условию:

Я пытался проверить это, используя, std::numeric_limits<float>::epsilon()но значение не удовлетворяет это, если вы попытаетесь получить предыдущее число с плавающей запятой с помощью std::nextafter:

#include <cmath>
#include <iostream>
#include <limits>

int main() {
    float e = std::numeric_limits<float>::epsilon();
    float previous = std::nextafter(e, -std::numeric_limits<float>::infinity());

    std::cout << std::boolalpha << ((1.0f + previous) > 1.0f) << std::endl;

    return 0;
}

Это кадры выходы true https://coliru.stacked-crooked.com/a/841e19dafcf0bf6f.

После попытки получить номер с помощью std::nextafterя заметил, что правильный Machine Epsilon должен быть:

std::nextafter(std::numeric_limits<float>::epsilon() / 2.0f, std::numeric_limits<float>::infinity())

Я тестировал это с помощью этого кода:

#include <cmath>
#include <iostream>
#include <limits>

bool verify(float e) {
    return ((1.0f + e) > 1.0f);
}

int main() {
    std::cout.precision(std::numeric_limits<float>::digits);
    std::cout << std::boolalpha << std::fixed;

    float epsilon = std::numeric_limits<float>::epsilon();

    float last = epsilon;
    while (true) {
        last = std::nextafter(last, -std::numeric_limits<float>::infinity());
        if ((1.0f + last) > 1.0f) {
            epsilon = last;
        } else {
            break;
        }
    }

    // Does not satisfy condition
    std::cout << "last: " << verify(last) << " " << last << std::endl;
    // Satisfy condition
    std::cout << "epsilon: " << verify(epsilon) << " " << epsilon << std::endl;

    float half_epsilon = std::numeric_limits<float>::epsilon() / 2.0f;
    float actual_epsilon = std::nextafter(half_epsilon, std::numeric_limits<float>::infinity());
    // Same as 'last' at this point
    std::cout << "half_epsilon: " << verify(half_epsilon) << " " << half_epsilon << std::endl;
    // Same as 'epsilon' at this point
    std::cout << "actual_epsilon: " << verify(actual_epsilon) << " " << actual_epsilon << std::endl;

    return 0;
}

Это выводит

last: false 0.000000059604644775390625
epsilon: true 0.000000059604651880817983
half_epsilon: false 0.000000059604644775390625
actual_epsilon: true 0.000000059604651880817983

https://coliru.stacked-crooked.com/a/3c66a2144e80a91b

Я что-то здесь упустил?

Ответы

6 JaMiT Dec 24 2020 at 11:46

Если я не ошибаюсь, определение Machine Epsilon - это наименьшее число, удовлетворяющее условию: [ 1 + epsilon > 1]

Близко, но вы ошибаетесь в контексте C ++. (Я считаю, что ваше определение верно в других, более академических контекстах.) Согласно cppreference.com , машинный эпсилон - это «разница между 1.0и следующим значением, представляемым [указанным] типом с плавающей запятой». Машинный эпсилон удовлетворяет 1 + epsilon > 1, но это не обязательно должно быть наименьшее число, которое удовлетворяет этому. Однако это наименьшее число, которое удовлетворяет этому условию во всех режимах округления .

Поскольку машинный эпсилон намного меньше чем 1.0, между эпсилон и 0.0. (Это основная цель представлений с плавающей запятой.) Когда любое из них добавляется 1.0, результат не представляется возможным, поэтому результат необходимо округлить. Если режим округления - до ближайшего представимого значения, тогда эта сумма будет округляться до тех пор, пока 1 + epsilonнебольшое число находится между epsilon/2и 3*epsilon/2. С другой стороны, если режим округления всегда приближается к нулю, вы получаете ожидаемый результат.

Попробуйте добавить #include <cfenv>в свой код следующую строку.

fesetround(FE_TOWARDZERO);

Это приводит к тому, что любая сумма строго от 1.0и 1 + epsilonдо округляется до 1.0. Теперь вы должны увидеть, как машина epsilon ведет себя так, как вы ожидали.

Другие режимы гарантированного округления - в сторону-бесконечности и в сторону + бесконечности. Подробности см. На cppreference.com .