İki eşit olmayan kayan nokta sayısını çıkararak 0 elde etmek mümkün müdür?


131

Aşağıdaki örnekte 0'a (veya sonsuza) bölme elde etmek mümkün mü?

public double calculation(double a, double b)
{
     if (a == b)
     {
         return 0;
     }
     else
     {
         return 2 / (a - b);
     }
}

Normal durumlarda elbette olmayacak. Ama ne olursa ave bçok yakın olabilir edilir (a-b)bırakılmasına neden 0hesaplamanın kesinlik nedeniyle?

Bu sorunun Java için olduğunu unutmayın, ancak çoğu programlama dili için geçerli olacağını düşünüyorum.


49
Tüm
dublör

3
@Thirler, JUnit Test'i kullanma zamanı gibi geliyor bana!
Matt Clark

7
@ bluebrain, benim tahminim, sizin gerçek sayınız 2.000 vb. bir float ile temsil edilecek birçok ondalık sayı içeriyor. Dolayısıyla, sonuncular karşılaştırmada kullanılan gerçek sayı ile temsil edilmeyecektir.
Thirler

4
@Thirler muhtemelen. 'float veya double'a atadığınız sayının kesin olduğunu gerçekten garanti edemezsiniz'
guness

4
Bu durumda 0 döndürmenin hata ayıklaması zor bir belirsizliğe yol açabileceğini unutmayın, bu nedenle bir istisna atmak veya bir NaN döndürmek yerine gerçekten 0 döndürmek istediğinizden emin olun.
m0skit0

Yanıtlar:


132

Java'da, a - bkarşı eşit asla 0eğer a != b. Bunun nedeni, Java'nın normalleştirilmiş sayıları destekleyen IEEE 754 kayan nokta işlemlerini zorunlu kılmasıdır. Gönderen spec :

Özellikle, Java programlama dili, IEEE 754 denormalize edilmiş kayan nokta sayılarının ve aşamalı alt akış desteğini gerektirir, bu da belirli sayısal algoritmaların istenen özelliklerinin kanıtlanmasını kolaylaştırır. Hesaplanan sonuç normalize edilmemiş bir sayı ise, kayan nokta işlemleri "sıfıra gelmez".

Bir FPU normalleştirilmiş sayılarla çalışıyorsa , eşit olmayan sayıları çıkarmak asla sıfır üretemez (çarpmanın aksine), bu soruya da bakın .

Diğer diller için değişir. C veya C ++ 'da, örneğin, IEEE 754 desteği isteğe bağlıdır.

Bununla birlikte, ifadenin 2 / (a - b)taşması mümkündür , örneğin a = 5e-308ve ile b = 4e-308.


4
Ancak OP 2 / (ab) hakkında bilgi almak istiyor. Bunun sonlu olması garanti edilebilir mi?
Taemyr

Cevabınız için teşekkürler, normalleştirilmiş sayıların açıklaması için wikipedia'ya bir bağlantı ekledim.
Thirler

3
@Taemyr Düzenlememe bakın. Bölünme aslında taşabilir.
nwellnhof

@Taemyr (a,b) = (3,1)=> 2/(a-b) = 2/(3-1) = 2/2 = 1Bunun IEEE kayan noktayla doğru olup olmadığını bilmiyorum
Cole Johnson

1
@DrewDormann IEEE 754, C99 için de isteğe bağlıdır. Standardın Ek F'sine bakın.
nwellnhof

50

Geçici bir çözüm olarak aşağıdakiler ne olacak?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}

Bu şekilde, herhangi bir dilde IEEE desteğine bağlı kalmazsınız.


6
Sorunu önleyin ve testi tek seferde basitleştirin. Benim gibi.
Joshua

11
-1 Eğer a=bgeri dönmemelisin 0. 0IEEE 754'e bölmek size bir istisna değil sonsuzluk kazandırır. Sorundan kaçıyorsunuz, bu yüzden geri dönüş 0, olmasını bekleyen bir hatadır. Düşünün 1/x + 1. Eğer x=0, bu 1doğru değerle sonuçlanmazsa : sonsuz.
Cole Johnson

5
@ColeJohnson da doğru cevap sonsuz değildir (sınırın hangi taraftan geldiğini belirtmediğiniz sürece, sağ taraf = + inf, sol taraf = -inf, belirtilmemiş = tanımsız veya NaN).
Nick T

12
@ChrisHayes: Bu, sorunun bir XY problemi olabileceğini kabul eden soruya geçerli bir cevaptır: meta.stackexchange.com/questions/66377/what-is-the-xy-problem
slebetman

17
@ColeJohnson Geri Dönen 0gerçekten sorun değil. OP'nin soruda yaptığı budur. Bloğun o kısmındaki duruma bir istisna veya uygun olanı koyabilirsiniz. Geri dönmekten hoşlanmıyorsanız 0, bu sorunun eleştirilmesi gerekir. Kesinlikle, OP'nin yaptığı gibi yapmak, cevaba olumsuz bir oy vermeyi garanti etmez. Bu sorunun, verilen işlev tamamlandıktan sonra başka hesaplamalarla ilgisi yoktur. Tüm bildiğiniz için, programın gereksinimleri geri dönmeyi gerektiriyor 0.
jpmc26

25

Değeri ne olursa olsun sıfıra bölme elde edemezsiniz a - bKayan nokta 0'a bölme bir istisna edemezsiniz. Sonsuzluğu döndürür.

Şimdi, a == btrue değerini döndürmenin tek yolu , aynı bitleri içermesi ave biçermesidir. Sadece en önemsiz bit kadar farklılarsa, aralarındaki fark 0 olmayacaktır.

DÜZENLE :

Bathsheba'nın doğru bir şekilde yorumladığı gibi, bazı istisnalar vardır:

  1. Kendisiyle "false" değerini "karşılaştırmaz" ancak aynı bit modellerine sahip olacaktır.

  2. -0.0, doğru ile +0.0'ı karşılaştırmak için tanımlanır ve bit kalıpları farklıdır.

Hem Yani eğer ave bvardır Double.NaN, başka maddesini ulaşacak, ancak o zamandan beri NaN - NaNde getiri NaN, sıfıra bölme işlemi olmayacaktır.


11
Eran; kesinlikle doğru değil. Kendisiyle "false" değerini "karşılaştırmaz" ancak aynı bit modellerine sahip olacaktır. Ayrıca -0.0, doğru ile +0.0'ı karşılaştırmak için tanımlanır ve bit kalıpları farklıdır.
Bathsheba

1
@Bathsheba Bu özel durumları dikkate almadım. Yorum için teşekkürler.
Eran

2
@Eran, çok iyi bir nokta, 0'a bölmenin kayan bir noktada sonsuza döneceğini. Soruya eklendi.
Thirler

2
@Prashant, ancak bu durumda bölünme gerçekleşmez, çünkü a == b true döner.
Eran

3
Aslında sıfıra bölme için bir FP istisnası elde edebilirsiniz , bu IEEE-754 standardı tarafından tanımlanan bir seçenektir, ancak muhtemelen çoğu insanın "istisna" ile kastettiği şey değildir;)
Voo

17

Burada sıfıra bölmenin olabileceği bir durum yoktur.

SMT Çözücü Z3 kayar nokta aritmetik hassas IEEE destekler. Z3'ten sayıları bulmasını isteyelim ave bşöyle a != b && (a - b) == 0:

(set-info :status unknown)
(set-logic QF_FP)
(declare-fun b () (FloatingPoint 8 24))
(declare-fun a () (FloatingPoint 8 24))
(declare-fun rm () RoundingMode)
(assert
(and (not (fp.eq a b)) (fp.eq (fp.sub rm a b) +zero) true))
(check-sat)

Sonuç UNSAT . Böyle bir numara yok.

Yukarıdaki SMTLIB dizisi ayrıca Z3'ün rastgele bir yuvarlama modu ( rm) seçmesine izin verir . Bu, sonucun tüm olası yuvarlama modları için geçerli olduğu anlamına gelir (bunlardan beş tane vardır). Sonuç, oyundaki değişkenlerden herhangi birinin NaNsonsuz olma olasılığını da içerir .

a == bolarak uygulanır fp.eqve böylece kalite +0fve -0feşit karşılaştırın. Sıfırla karşılaştırma da kullanılarak gerçekleştirilir fp.eq. Soru, sıfıra bölünmeden kaçınmayı amaçladığından, bu uygun bir karşılaştırmadır.

Eşitlik testi bitsel eşitlik kullanılarak uygulanmış olsaydı +0fve -0fbunu yapmanın bir yolu olurdua - b sıfır . Bu cevabın yanlış bir önceki versiyonu, meraklı için bu durumla ilgili mod ayrıntılarını içerir.

Z3 Online henüz FPA teorisini desteklemiyor. Bu sonuç, en son kararsız dal kullanılarak elde edildi. Aşağıdaki gibi .NET bağlamaları kullanılarak çoğaltılabilir:

var fpSort = context.MkFPSort32();
var aExpr = (FPExpr)context.MkConst("a", fpSort);
var bExpr = (FPExpr)context.MkConst("b", fpSort);
var rmExpr = (FPRMExpr)context.MkConst("rm", context.MkFPRoundingModeSort());
var fpZero = context.MkFP(0f, fpSort);
var subExpr = context.MkFPSub(rmExpr, aExpr, bExpr);
var constraintExpr = context.MkAnd(
        context.MkNot(context.MkFPEq(aExpr, bExpr)),
        context.MkFPEq(subExpr, fpZero),
        context.MkTrue()
    );

var smtlibString = context.BenchmarkToSMTString(null, "QF_FP", null, null, new BoolExpr[0], constraintExpr);

var solver = context.MkSimpleSolver();
solver.Assert(constraintExpr);

var status = solver.Check();
Console.WriteLine(status);

O (örneğin davaları göz ardı etmek zordur çünkü IEEE şamandıra sorulara cevap Z3 kullanarak güzel NaN, -0f, +-inf) ile keyfi sorular sorabilirsiniz. Spesifikasyonları yorumlamaya ve alıntı yapmaya gerek yok. "Bu belirli int log2(float)algoritma doğru mu?" Gibi karışık kayan nokta ve tamsayı soruları bile sorabilirsiniz .


Lütfen SMT Solver Z3'e ve çevrimiçi tercümana bir bağlantı ekleyebilir misiniz? Bu cevap tamamen yasal görünse de, birisi bu sonuçların yanlış olduğunu düşünebilir.
AL

12

Sağlanan işlev gerçekten de sonsuz döndürebilir:

public class Test {
    public static double calculation(double a, double b)
    {
         if (a == b)
         {
             return 0;
         }
         else
         {
             return 2 / (a - b);
         }
    }    

    /**
     * @param args
     */
    public static void main(String[] args) {
        double d1 = Double.MIN_VALUE;
        double d2 = 2.0 * Double.MIN_VALUE;
        System.out.println("Result: " + calculation(d1, d2)); 
    }
}

Çıktı Result: -Infinity.

Bölmenin sonucu, ikiye katlanacak kadar büyük olduğunda, payda sıfırdan farklı olsa bile sonsuz döndürülür.


6

IEEE-754'e uyan bir kayan nokta uygulamasında, her bir kayan nokta türü iki formatta sayıları tutabilir. Bir ("normalleştirilmiş") çoğu kayan nokta değeri için kullanılır, ancak temsil edebileceği ikinci en küçük sayı, en küçük olandan yalnızca küçük bir parça daha büyüktür ve bu nedenle aralarındaki fark aynı formatta gösterilemez. Diğer ("normalsizleştirilmiş") biçim, yalnızca ilk biçimde gösterilemeyen çok küçük sayılar için kullanılır.

Normalden arındırılmış kayan nokta biçimini verimli bir şekilde işlemek için kullanılan devreler pahalıdır ve tüm işlemciler bunu içermez. Bazı işlemciler olmak gerçekten küçük sayılar üzerinde ya sahip operasyonlar arasında bir seçim sunan çok üzerinde işlemlerin diğer değerler üzerindeki işlemlerden daha yavaş olması ya da işlemcinin normalleştirilmiş format için çok küçük olan sayıları sıfır olarak kabul etmesi .

Java spesifikasyonları, uygulamaların, kodun daha yavaş çalışmasını sağlayacak makinelerde bile normalleştirilmiş formatı desteklemesi gerektiğini ima eder. Öte yandan, bazı uygulamaların, çoğu amaç için önemli olamayacak kadar küçük olan değerlerin biraz özensiz işlenmesi karşılığında kodun daha hızlı çalışmasına izin veren seçenekler sunması mümkündür (değerlerin önemsiz olduğu durumlarda, önemli olan hesaplamalar on kat daha uzun sürdüğü için hesaplamalar yapmak can sıkıcı olabilir, bu nedenle birçok pratik durumda sıfıra sıfır yavaş ama doğru aritmetikten daha kullanışlıdır).


6

IEEE 754'ten önceki eski zamanlarda, a! = B'nin ab! = 0 anlamına gelmemesi ve bunun tersi mümkündü. İlk etapta IEEE 754'ü yaratmanın nedenlerinden biri buydu.

IEEE 754 ile neredeyse garantilidir. C veya C ++ derleyicilerinin gerekenden daha yüksek hassasiyetle işlem yapmalarına izin verilir. Dolayısıyla, a ve b değişken değil, ifade ise, (a + b)! = C (a + b) - c! = 0 anlamına gelmez, çünkü a + b bir kez daha yüksek hassasiyetle ve bir kez olmadan hesaplanabilir daha yüksek hassasiyet.

Birçok FPU, normalize edilmemiş sayıları döndürmeyen, ancak onları 0 ile değiştirdikleri bir moda geçirilebilir. Bu modda, a ve b, farkın normalleştirilmiş en küçük sayıdan küçük ancak 0'dan büyük olduğu küçük normalleştirilmiş sayılarsa, a ! = b ayrıca a == b'yi garanti etmez.

"Kayan noktalı sayıları asla karşılaştırma" kargo kült programlamadır. "Bir epsilona ihtiyacınız var" mantrasına sahip olanların çoğunun o epsilonu nasıl doğru seçecekleri hakkında hiçbir fikri yok.


2

Bunun olmasına neden olabileceğiniz bir vaka düşünebilirim . İşte baz 10'daki benzer bir örnek - gerçekten, bu tabii ki 2 numaralı tabanda olur.

Kayan nokta sayıları aşağı yukarı bilimsel gösterimde depolanır - yani, 35.2 görmek yerine, depolanan sayı daha çok 3.52e2 olacaktır.

Kolaylık sağlamak adına 10 tabanında çalışan ve 3 basamaklı doğruluğu olan bir kayan nokta birimimiz olduğunu hayal edin. 10.0'dan 9.99'u çıkardığınızda ne olur?

1.00e2-9.99e1

Her bir değere aynı üs vermek için Shift

1.00e2-0.999e2

3 haneye yuvarlayın

1.00e2-1.00e2

Uh oh!

Bunun gerçekleşip gerçekleşmeyeceği, nihai olarak FPU tasarımına bağlıdır. Bir çift için üs aralığı çok büyük olduğundan, donanımın bir noktada dahili olarak yuvarlanması gerekir, ancak yukarıdaki durumda dahili olarak sadece 1 ekstra rakam herhangi bir sorunu önleyecektir.


1
Çıkarma için hizalanmış işlenenleri tutan yazmaçların, bu durumla başa çıkmak için "koruma bitleri" adı verilen fazladan iki biti tutması gerekir. Çıkarmanın en anlamlı bitten ödünç almaya neden olacağı senaryoda, ya küçük işlenenin büyüklüğü, büyük işlenenin yarısını geçmelidir (yalnızca bir ekstra bit duyarlığına sahip olabileceğini ima eder) ya da sonuç en az olmalıdır daha küçük işlenenin yarısı kadardır (sadece bir bit daha gerekeceği anlamına gelir, artı doğru yuvarlamayı sağlamak için yeterli bilgi).
supercat

1
"Bunun gerçekleşip gerçekleşmeyeceği nihai olarak FPU tasarımına bağlıdır" Hayır, bu olamaz çünkü Java tanımı yapamayacağını söylüyor. FPU tasarımının bununla hiçbir ilgisi yoktur.
Pascal Cuoq

@PascalCuoq: Yanılıyorsam düzeltin, ancak strictfpetkinleştirilmemişse, hesaplamaların için çok küçük olan doubleancak genişletilmiş hassas kayan nokta değerine sığacak değerler vermesi mümkündür .
supercat

@supercat yokluğu strictfpyalnızca “ara sonuçların” değerlerini etkiler ve ben docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4'ten alıntı yapıyorum . ave bvardır doubledeğerleri çift kesin değerler, yani değişken değil, ara sonuçlar, böylece 2 ^ -1074 katlarıdır,. Bu iki çift kesinlik değerinin çıkarılması sonuç olarak 2 ^ -1074'ün katıdır, bu nedenle daha geniş üs aralığı, a == b dışında farkın 0 olduğu özelliğini değiştirir.
Pascal Cuoq

@supercat Bu mantıklı - bunu yapmak için fazladan bir parçaya ihtiyacınız var.
Keldor314

1

Eşitlik için float veya double'ı asla karşılaştırmamalısınız; çünkü, float veya double'a atadığınız sayının tam olduğunu garanti edemezsiniz.

Eşitlik için kayan sayıları mantıklı bir şekilde karşılaştırmak için, değerin aynı değere "yeterince yakın" olup olmadığını kontrol etmeniz gerekir:

if ((first >= second - error) || (first <= second + error)

6
"Hiç yapmamalı" biraz güçlü, ama genellikle bu iyi bir tavsiye.
Mark Pattison

1
Doğru olduğunuz halde, abs(first - second) < error(veya <= error) daha kolay ve daha özlüdür.
glglgl

3
Çoğu durumda ( hepsi değil ) doğru olsa da , soruyu gerçekten yanıtlamaz.
milleniumbug

4
Kayan noktalı sayıları eşitlik için test etmek oldukça yararlıdır. Dikkatlice seçilmemiş bir epsilon ile karşılaştırmanın aklı başında hiçbir şey yoktur ve eşitlik için test edilirken epsilon ile kıyaslama konusunda daha az aklı başında hiçbir şey yoktur.
tmyklebu

1
Bir diziyi bir kayan nokta anahtarında sıralarsanız, kayan noktalı sayıları bir epsilon ile karşılaştıran hileler kullanmaya çalışırsanız kodunuzun çalışmayacağını garanti edebilirim. Çünkü a == b ve b == c garantisi, a == c'nin artık olmadığını ima ediyor. Karma tablolar için tam olarak aynı sorun. Eşitlik geçişli olmadığında, algoritmalarınız bozuluyor.
gnasher729

1

Sıfıra bölme tanımsızdır, çünkü pozitif sayılardan gelen sınır sonsuza, negatif sayılarla sınırlı olan negatif sonsuza eğilimlidir.

Dil etiketi olmadığından, bunun C ++ mı yoksa Java mı olduğundan emin değilim.

double calculation(double a, double b)
{
     if (a == b)
     {
         return nan(""); // C++

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}

1

Temel sorun, "çok fazla" ondalık sayıya sahip olduğunuzda, örneğin sayısal bir değer ( pi veya 1 / 3'ün sonucu).

Öyleyse a == b, a ve b'nin herhangi bir çift değeriyle yapılamaz, a = 0.333 ve b = 1/3 iken a == b ile nasıl başa çıkarsınız? İşletim sisteminize, FPU'ya, sayıya karşı dile, 0'dan sonra 3'e bağlı olarak doğru veya yanlış olacaktır.

Her neyse, bir bilgisayarda "çifte değer hesaplaması" yaparsanız, doğrulukla uğraşmanız gerekir, bu yüzden yapmak yerine a==byapmanız gerekir absolute_value(a-b)<epsilonve epsilon, algoritmanızda o anda modellediğiniz şeye bağlıdır. Tüm çift karşılaştırmalarınız için epsilon değerine sahip olamazsınız.

Kısaca, a == b yazdığınızda, bir bilgisayarda çevrilemeyen matematiksel bir ifadeye sahip olursunuz (herhangi bir kayan nokta sayısı için).

Not: uğultu, burada cevapladığım her şey az ya da çok başkalarının yanıtlarında ve yorumlarında.


1

@Malarres yanıtı ve @Taemyr yorumuna dayanarak, işte benim küçük katkım:

public double calculation(double a, double b)
{
     double c = 2 / (a - b);

     // Should not have a big cost.
     if (isnan(c) || isinf(c))
     {
         return 0; // A 'whatever' value.
     }
     else
     {
         return c;
     }
}

Demek istediğim şunu söylemektir: Bölmenin sonucunun nan veya inf olup olmadığını anlamanın en kolay yolu bölmeyi gerçekleştirmek için gerçekte.

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.