0'a eşitlik için kayan nokta değerlerini kontrol etmek güvenli midir?


100

Normalde çift veya ondalık tür değerleri arasındaki eşitliğe güvenemeyeceğinizi biliyorum, ancak 0'ın özel bir durum olup olmadığını merak ediyorum.

0,00000000000001 ve 0,00000000000002 arasındaki belirsizlikleri anlayabilsem de, 0'ın kendisini karıştırması oldukça zor görünüyor çünkü hiçbir şey değil. Hiçbir şey konusunda kesin değilseniz, artık hiçbir şey değil.

Ama bu konu hakkında pek bir şey bilmiyorum, bu yüzden söylemem bana göre değil.

double x = 0.0;
return (x == 0.0) ? true : false;

Bu her zaman doğru mu dönecek?


69
Üçlü operatör bu kodda gereksiz :)
Joel Coehoorn

5
LOL haklısın. Git bana
Gene Roberts

Bunu yapmam çünkü x'in nasıl sıfıra ayarlandığını bilmiyorsun. Hala yapmak istiyorsanız, 1e-12'den veya sonunda etiketlenmiş olabileceklerden kurtulmak için x'i yuvarlamak veya katlamak isteyebilirsiniz.
Rex Logan

Yanıtlar:


115

Öyle güvenli karşılaştırma döneceğini beklemek trueve çift değişken tam bir değere sahip olması gerekir, eğer 0.0(orijinal kod parçacığı olduğunu, tabii ki, durum). Bu, ==operatörün anlambilimiyle tutarlıdır . a == b" aeşittirb .

Öyle güvenli değil (çünkü gerçek doğru değil ) bazı hesaplamanın sonucu saf Matematik aynı hesaplamanın sonucu sıfır olduğunda çift (ya da daha genel olarak, kayan nokta) aritmetiğin içinde sıfır olacağı beklemek. Bunun nedeni, hesaplamalar zemine geldiğinde, matematikte Gerçek sayı aritmetiğinde bulunmayan bir kavram olan kayan nokta hassas hatası ortaya çıkmasıdır.


51

Çok sayıda "eşitlik" karşılaştırması yapmanız gerekiyorsa, aşağıdakileri karşılaştırmak için .NET 3.5'te küçük bir yardımcı işlev veya genişletme yöntemi yazmak iyi bir fikir olabilir:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

Bu şu şekilde kullanılabilir:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);

4
Bu sayıların birbirine çok yakın değerlere sahip olması durumunda, double1 ve double2'yi karşılaştırarak eksiltme iptal hatası yaşıyor olabilirsiniz. Math.Ab'leri kaldırır ve her dalı ayrı ayrı kontrol ederdim d1> = d2 - e ve d1 <= d2 + e
Theodore Zographos

"Epsilon, aralığı sıfıra yakın olan pozitif bir değerin minimum ifadesini tanımladığından, iki benzer değer arasındaki fark marjı Epsilon'dan daha büyük olmalıdır. Tipik olarak, Epsilon'dan birçok kez daha büyüktür. Bu nedenle, bunu yapmanızı öneririz. Eşitlik için Double değerlerini karşılaştırırken Epsilon kullanmayın. " - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
Rafael Costa

15

Basit örneğiniz için bu test tamamdır. Ama buna ne dersin?

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

Unutmayın ki .1 ikili olarak tekrar eden bir ondalık sayıdır ve tam olarak temsil edilemez. Sonra bunu şu kodla karşılaştırın:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

Gerçek sonuçları görmek için sizi bir test yapmaya bırakacağım: bu şekilde hatırlama olasılığınız daha yüksektir.


5
Aslında, bu bazı nedenlerden dolayı doğru döndürür (en azından LINQPad'de).
Alexey Romanov

Bahsettiğiniz ".1 sorunu" nedir?
Teejay

14

Double.Equals için MSDN girişinden :

Karşılaştırmalarda Kesinlik

Eşittir yöntemi dikkatli kullanılmalıdır, çünkü görünüşte eşdeğer iki değer, iki değerin farklı kesinliği nedeniyle eşit olmayabilir. Aşağıdaki örnek, Double değeri .3333 ve 1'i 3'e bölerek döndürülen Double değerinin eşit olmadığını bildirmektedir.

...

Eşitlik için karşılaştırmak yerine, önerilen bir teknik, iki değer arasında kabul edilebilir bir fark marjının tanımlanmasını içerir (değerlerden birinin% 0,01'i gibi). İki değer arasındaki farkın mutlak değeri bu marjdan küçükse veya ona eşitse, fark muhtemelen hassasiyetteki farklılıklardan kaynaklanmaktadır ve bu nedenle değerler büyük olasılıkla eşit olacaktır. Aşağıdaki örnek, önceki kod örneğinde eşit olmayan iki Double değeri olan .33333 ve 1 / 3'ü karşılaştırmak için bu tekniği kullanır.

Ayrıca Double.Epsilon'a bakınız .


1
Oldukça eşdeğer olmayan değerlerin eşit olarak karşılaştırılması da mümkündür. Bir eğer beklediğiniz x.Equals(y), o (1/x).Equals(1/y)takdirde, ama bu böyle değil xise 0ve yolduğu 1/Double.NegativeInfinity. Karşılıklı olmamakla birlikte bu değerler eşittir.
supercat

@supercat: Eşdeğerler. Ve karşılıkları yok. Testinizi x = 0ve ile tekrar çalıştırabilir y = 0ve yine de bulursunuz 1/x != 1/y.
Ben Voigt

@BenVoigt: With xve ytürü gibi double? Eşitsiz olduklarını bildirmek için sonuçları nasıl karşılaştırırsınız? 1 / 0.0'ın NaN olmadığını unutmayın.
supercat

@supercat: Tamam, IEEE-754'ün yanlış yaptığı şeylerden biri. (Birincisi, 1.0/0.0limit benzersiz olmadığı için olması gerektiği gibi NaN olamaz. İkincisi, sonsuzluk derecelerine hiç dikkat etmeden sonsuzlukların birbirine eşit olması)
Ben Voigt

@BenVoigt: Sıfır, çok küçük iki sayının çarpılmasının bir sonucuysa, 1.0'ı buna bölmek, küçük sayıların herhangi bir sayısının aynı işarete sahip olduğundan daha büyük ve küçüklerden biri varsa herhangi bir sayıdan daha küçük bir değer vermelidir. sayıların zıt işaretleri vardı. IMHO, IEEE-754, işaretsiz sıfır, ancak pozitif ve negatif sonsuz küçüklere sahip olsaydı daha iyi olurdu.
supercat

6

Sorun, farklı kayan noktalı değer uygulaması türlerini karşılaştırırken ortaya çıkar, örneğin float ile double'ı karşılaştırırken. Ancak aynı tipte bir sorun olmamalı.

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

Sorun şu ki, programcı bazen karşılaştırma için örtük tip atamasının (çift yüzeye çift) gerçekleştiğini ve bunun bir hataya dönüştüğünü unutuyor.


3

Sayı doğrudan şamandıraya atanmışsa veya çift ise, sıfıra veya bir çift için 53 bit veya bir kayan nokta için 24 bit olarak gösterilebilen herhangi bir tam sayıya karşı test etmek güvenlidir.

Ya da başka bir şekilde ifade etmek gerekirse, her zaman bir ikiye katlama ve tamsayı değeri atayabilir ve ardından ikiliyi aynı tam sayı ile karşılaştırabilir ve eşit olacağı garanti edilir.

Ayrıca bir tam sayı atayarak başlayabilir ve tam sayılarla toplama, çıkarma veya çarpma işlemlerine bağlı kalarak basit karşılaştırmaların çalışmaya devam etmesini sağlayabilirsiniz (sonucun bir kayan nokta için 24 bitten az olduğu ve bir çift için 53 bit olduğu varsayılarak). Böylece, belirli kontrollü koşullar altında kayan sayıları ve çiftleri tam sayı olarak ele alabilirsiniz.


Genel olarak beyanınıza katılıyorum (ve ona olumlu oy verdim) ancak bunun gerçekten IEEE 754 kayan nokta uygulamasının kullanılıp kullanılmadığına bağlı olduğuna inanıyorum. Ve her "modern" bilgisayarın, en azından şamandıraların depolanması için IEEE 754 kullandığına inanıyorum (farklı olan garip yuvarlama kuralları vardır).
Mark Lakata

2

Hayır, tamam değil. Sözde normalleştirilmiş değerler (normal altı), 0.0'a eşit olarak karşılaştırıldığında, yanlış (sıfır olmayan) olarak karşılaştırılır, ancak bir denklemde kullanıldığında normalize edilir (0.0 olur). Bu nedenle, sıfıra bölmeyi önlemek için bunu bir mekanizma olarak kullanmak güvenli değildir. Bunun yerine 1.0 ekleyin ve 1.0 ile karşılaştırın. Bu, tüm alt normallerin sıfır olarak değerlendirilmesini sağlayacaktır.


Subnormals aynı zamanda denormals
Manuel

Alt normaller, kullanıldıklarında sıfıra eşit olmazlar, ancak kesin işleme bağlı olarak aynı sonucu verebilirler veya vermeyebilirler.
wnoise

-2

Bunu deneyin ve == 'un double / float için güvenilir olmadığını göreceksiniz.
double d = 0.1 + 0.2; bool b = d == 0.3;

İşte Quora'nın cevabı .


-4

Aslında, bir çift değeri 0.0 ile karşılaştırmak için aşağıdaki kodları kullanmanın daha iyi olacağını düşünüyorum:

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

Şamandıra için aynı:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;

5
Hayır. Double.Epsilon'daki belgelerden: "İki kayan noktalı sayının eşit kabul edilip edilemeyeceğini belirleyen özel bir algoritma oluşturursanız, kabul edilebilir mutlak fark marjını oluşturmak için Epsilon sabitinden daha büyük bir değer kullanmanız gerekir iki değerin eşit kabul edilmesi için. (Tipik olarak, bu fark marjı Epsilon'dan birçok kez daha büyüktür.) "
Alastair Maw

1
@AlastairMaw bu, eşitlik için herhangi bir boyutun iki çiftini kontrol etmek için geçerlidir. Sıfıra eşitliği kontrol etmek için, iki kat, Epsilon iyidir.
jwg

4
Hayır değil . Bazı hesaplamalarla ulaştığınız değer büyük olasılıkla sıfırdan epsilon uzaktadır, ancak yine de sıfır olarak kabul edilmelidir. Sırf sıfıra yakın olduğu için, bir yerden ara sonucunuzda sihirli bir şekilde bir sürü ekstra hassasiyet elde edemezsiniz.
Alastair Maw

4
Örneğin: (1.0 / 5.0 + 1.0 / 5.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0) <double.Epsilon == false (ve büyük ölçüde şu şekilde: 2.78E-17 vs 4.94E -324)
Alastair Maw

öyleyse, double.Epsilon uygun değilse, önerilen hassasiyet nedir? 10 kere epsilon tamam mı? 100 kere?
liang
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.