Mengapa numeric_limits Machine Epsilon tidak memenuhi kondisi 1 + e> 1?

Dec 24 2020

Jika saya tidak salah definisi Mesin Epsilon adalah angka terendah yang memenuhi kondisi:

Saya mencoba menguji ini dengan menggunakan std::numeric_limits<float>::epsilon()tetapi nilainya tidak memuaskan ini, jika Anda mencoba mendapatkan nomor float sebelumnya dengan 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;
}

Ini masih menghasilkan keluaran true https://coliru.stacked-crooked.com/a/841e19dafcf0bf6f.

Setelah mencoba mendapatkan nomor menggunakan std::nextaftersaya perhatikan bahwa Mesin Epsilon yang tepat harus:

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

Saya mengujinya menggunakan kode ini:

#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;
}

Output ini

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

Apakah saya melewatkan sesuatu di sini?

Jawaban

6 JaMiT Dec 24 2020 at 11:46

Jika saya tidak salah definisi Mesin Epsilon adalah angka terendah yang memenuhi kondisi: [ 1 + epsilon > 1]

Tutup, tetapi Anda salah dalam konteks C ++. (Saya yakin definisi Anda benar dalam konteks lain yang lebih akademis.) Menurut cppreference.com , mesin epsilon adalah "perbedaan antara 1.0dan nilai berikutnya yang dapat direpresentasikan oleh tipe floating-point [ditentukan]". Mesin epsilon memang memuaskan 1 + epsilon > 1, tetapi tidak perlu angka terendah yang memuaskan itu. Namun, ini adalah angka terendah yang memenuhi kondisi tersebut di bawah semua mode pembulatan .

Karena mesin epsilon jauh lebih kecil daripada 1.0, ada banyak nilai yang dapat direpresentasikan antara epsilon dan 0.0. (Itu adalah tujuan dasar representasi floating point.) Ketika salah satu dari ini ditambahkan 1.0, hasilnya tidak dapat direpresentasikan, jadi hasilnya perlu dibulatkan. Jika mode pembulatan adalah ke nilai terwakili terdekat, maka jumlah itu akan dibulatkan ke 1 + epsilonsetiap kali angka kecil berada di antara epsilon/2dan 3*epsilon/2. Di sisi lain, jika mode pembulatan selalu menuju nol, maka Anda mendapatkan hasil yang Anda harapkan.

Coba tambahkan #include <cfenv>dan baris berikut ke kode Anda.

fesetround(FE_TOWARDZERO);

Hal ini menyebabkan penjumlahan ketat antara 1.0dan 1 + epsilonuntuk dibulatkan 1.0. Anda sekarang harus melihat mesin epsilon berperilaku seperti yang Anda harapkan.

Mode pembulatan terjamin lainnya menuju -infinity dan menuju + infinity. Lihat cppreference.com untuk detailnya.