なぜnumeric_limitsマシンイプシロンが1+ e> 1条件を満たさないのですか?

Dec 24 2020

私が間違っていなければ、マシンイプシロンの定義は次の条件を満たす最小の数値です。

私はこれを利用してこれをテスト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、適切なマシンイプシロンは次のようになるはずであることに気付きました。

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

私が間違っていなければ、マシンイプシロンの定義は次の条件を満たす最小の数です:[ 1 + epsilon > 1]

閉じますが、C ++のコンテキストでは間違っています。 (他のより学術的な文脈では、あなたの定義は正しいと思います。)cppreference.comによると、マシンイプシロンは「1.0[指定された]浮動小数点型で表される次の値との差」です。計算機イプシロンはを満たします1 + epsilon > 1が、それを満たす最小の数である必要はありません。ただし、これは、すべての丸めモードでその条件を満たす最小の数値です。

計算機イプシロンは、よりもはるかに小さいため1.0、イプシロンと0.0。の間には表現可能な値がたくさんあります。(これが浮動小数点表現の基本的な目標です。)これらのいずれかをに追加する1.0と、結果は表現できないため、結果を丸める必要があります。丸めモードは、最も近い表現可能な値にある場合は、その合計がに丸めます1 + epsilon少数の間にある時はいつでもepsilon/23*epsilon/2。一方、丸めモードが常にゼロに向かっている場合は、期待した結果が得られます。

#include <cfenv>次の行をコードに追加してみてください。

fesetround(FE_TOWARDZERO);

これにより、1.0との間の合計は厳密に1 + epsilonに丸められ1.0ます。これで、マシンイプシロンが期待どおりに動作するのがわかります。

他の保証された丸めモードは、-無限大および+無限大に向かっています。詳細については、cppreference.comを参照してください。