2016-12-24 48 views
59

Bir şamandıra için mümkün olan en küçük değeri bir kayan noktaya eklemek istiyorum. Yani, örneğin, I + mümkün olan en küçük şamandıra 1,0 almak için bunu denedi:Bir şamandıra mümkün olan en küçük yüzüğü ekleme

float result = 1.0f + std::numeric_limits<float>::min(); 

Ama şu sonuçları almak yaptıktan sonra:

(result > 1.0f) == false 
(result == 1.0f) == true 

Visual Studio 2015 kullanıyorum Bu neden oluyor? Bunu aşmak için ne yapabilirim?

+27

Neden şaşırdınız? Min ekliyorsunuz, epsilon değil. –

+6

Fark olduğunu fark etmemiştim! Her zaman eşdeğer olduklarını varsaymıştım. Teşekkürler, bu yardımcı oldu. – Squidy

+1

@Matteo Yanıt? Bu soru için gerçekten bir nedenim yok. –

cevap

86

Bir sonraki gösterilebilir değeri 1'den sonra isterseniz, std::nextafter için <cmath> başlığından bir işlev vardır.

float result = std::nextafter(1.0f, 2.0f); 

İkinci bağımsız değişken yönünde ilk argümandan başlayarak bir sonraki gösterilebilir değeri döndürür. Eğer 1'in altına sonraki değeri bulmak istiyorsa Yani, bu yapabilirdi:

float result = std::nextafter(1.0f, 0.0f); 

1 ve sonraki gösterilebilen değer arasındaki fark daha büyüktür çünkü çalışmıyor 1'e en küçük pozitif Temsil değeri ekleme 0 ile bir sonraki gösterilebilir değer arasındaki fark.

+18

'std :: numeric_limits :: min()' oldukça küçük bir pozitif değer değil; En küçük pozitif normalleştirilmiş değerdir, bu yüzden altnormaller daha düşük olabilir. – user2357112

+2

IIRC, tüm kayan noktalı bit-desenlerinin yaklaşık yarısı, 1.0 'den küçük olan bir sayıyı temsil eder. Tamsayı olarak, FP bit-kalıplarını sıralayan kodlama şeklini hesapladıktan sonra, üs alanın aralığı, '0' (mantis için bir '2^0 = 1.0' çarpanı temsil eden) etrafında az ya da çok ortalanmış durumdadır. aslında işe yarıyor. Bruce Dawson'un kayan nokta tuhaf şeyler hakkındaki makalelerini görmek için [bu sunum hakkında] (https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format /) –

+0

Bu makaledeki FP makalelerindeki içerik için [bu makale] bölümüne bakın (https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). –

20

min yaklaşık 2 -126 (-126 bir şamandıra için izin verilen minimum üs olan) (normalize edilmiş form) şamandıra varsayabiliriz küçük sıfır olmayan değeri, diğer bir deyişle bir şeydir; Şimdi, eğer 1'e toplarsanız, hala 1 alırsınız, çünkü bir float sadece 23 bit mantissa sahiptir, böylelikle küçük bir değişim böyle bir "büyük" sayıda temsil edilemez (126 bit mantis görmeniz gerekir) Bir değişiklik toplamı 2 -126 1). bu mantis son bit etkilediği -

1 mümkün olan en az değişikliği, bunun yerine, epsilon aslında 2 -23 içinde (sözde makine epsilon) 'dir.

+2

'std :: numeric_limits :: min()' en küçük pozitif * normalleştirilmiş * değeridir. Subnormaller daha düşük olabilir. – user2357112

+0

@ user2357112: Profilime bir uyarı ekleyelim: "Kayan nokta hakkında yaptığım herhangi bir tartışma, en iyi göz ardı edilen çirkin yaratıklar olan denormalize sayılar dikkate alınmadan yapılır" :-) –

+2

Altörneklerin eksikliği bile çok daha çirkin. Mevcut olmayan altnormaller ile iki eşit olmayan sayı her zaman sıfırdan farklı bir cevap verecektir. Mevcut altnormaller olmadan bunu yapmaz. – plugwash

40

Gözlemlediğiniz "sorun", çok doğa kayan nokta aritmetiğinden kaynaklanmaktadır.

FP'de hassasiyet, ölçeğe göre değişir; 1.0 değeri etrafında 1.0 ve 1.0+min_representable arasındaki farkı ayırt edebilmek için yeterli değildir, burada min_representable sıfırdan daha büyük olası en küçük değerdir (sadece en küçük normalleştirilmiş sayıyı, std::numeric_limits<float>::min()'u dikkate alsak bile, en küçük denormal büyüklük birkaç mertebedir.) daha küçük.

çift hassasiyetli 64 bit IEEE-754 kayan nokta sayıları ile Örneğin, x=10000000000000000 ölçeğinde (10) etrafında bu x ve x+1 ayırt etmek mümkün değildir.


ondalık nokta "yüzen" çünkü skala ile çözünürlüklü değişiklikleri, adı "kayan nokta" için çok nedeni olması. Bunun yerine sabit bir nokta gösterimi sabit bir çözünürlüğe sahip olacaktır (örneğin, 1/65536 ~ 0.00001 arasında bir hassasiyete sahip birimlerin 16 ikili rakamı ile).

floating point


küçük değeri: IEEE-754 32-bit kayan nokta biçiminde Örneğin

işareti bir bit, üs için 8 bit ve mantis için 31 bit orada eps, 1.0f + eps != 1.0fFLT_EPSILON veya std::numeric_limits<float>::epsilon olarak önceden tanımlanmış bir sabit olarak kullanılabilir. Ayrıca, epsilon'un yuvarlama hatalarıyla nasıl ilişkili olduğunu ele alan machine epsilon on Wikipedia'a da bakınız.

I.I.I.e. epsilon, burada beklediğinizi yapan ve 1.0'a eklendiğinde fark yaratan en küçük değerdir.

Bunun daha genel sürümü (1.0 dışındaki numaralar için) son yerde (mantissa) 1 birim olarak adlandırılır. Wikipedia'nın ULP article.

+5

Sorunun, "kayan nokta" sözcüğünü (ya da sadece "kayan") kullanan kişilerde, gerçek yüzer tabiatı göz önünde bulundurmadan (ya da hatta bilmeden) "bir bilgisayardaki tamsayı olmayan bir sayı" için kullanıldığını tahmin ediyorum. ölçeğinde). –

+0

Doğru. Eğer bu tür bir şeyden çok şey yapacaksa, kayan noktaların arkasındaki kavramları incelemek için biraz zaman harcamak iyi bir fikir olacaktır. Özellikle habersiz kullanıcı için ortaya çıkabilecek bir dizi "şaşırtıcı" etki var. –

+2

** 'eps', FLT_MIN ** için yanlış addır. 'eps', FLOAT_EPSILON' için kısadır, yani [1.0'a eklendiğinde fark yaratan en küçük sayı] (https://en.wikipedia.org/wiki/Machine_epsilon#Variant_definitions). '1.0' için son yerde (mantissa) 1 birim (bkz. [Ulp] (https://en.wikipedia.org/wiki/Unit_in_the_last_place)). Tanımladığınız şey epsilon ve 1 ULP kavramıdır, ancak sorun 'eps ​​= sıfırdan daha küçük olası değerdir'. –

5

Kayan nokta değerini mümkün olan en küçük miktarda artırmak/azaltmak için, nextafter'u +/- infinity() yönünde kullanın.

Yalnızca next_after(x,std::numeric_limits::max()) kullanırsanız, x durumunun hatalı olması durumunda sonuç yanlış olabilir.

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

template<typename T> 
T next_above(const T& v){ 
    return std::nextafter(1.0,std::numeric_limits<T>::infinity()) ; 
} 
template<typename T> 
T next_below(const T& v){ 
    return std::nextafter(1.0,-std::numeric_limits<T>::infinity()) ; 
} 

int main(){ 
    std::cout << next_below(1.0) - 1.0<< std::endl; // gives eps 
    std::cout << next_above(1.0) - 1.0<< std::endl; // gives ~ -eps/2 

    // Note: 
    std::cout << std::nextafter(std::numeric_limits<double>::infinity(), 
    std::numeric_limits<double>::infinity()) << std::endl; // gives inf 
    std::cout << std::nextafter(std::numeric_limits<double>::infinity(), 
    std::numeric_limits<double>::max()) << std::endl; // gives 1.79769e+308 

} 
İlgili konular