Если я не ошибаюсь, определение машины Эпсилон — это наименьшее число, удовлетворяющее условию:
Я пытался проверить это, используя 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
Я что-то пропустил здесь?
Если я не ошибаюсь, определение Машинного Эпсилона — это наименьшее число, удовлетворяющее условию: [
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.