Czy można uzyskać 0 przez odjęcie dwóch nierównych liczb zmiennoprzecinkowych?

Czy w poniższym przykładzie można uzyskać podział przez 0 (lub nieskończoność)?

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

W normalnych przypadkach nie będzie, oczywiście. Ale co jeśli a i b są bardzo blisko, czy (a-b) może wynikać 0 ze względu na precyzję obliczeń?

Zauważ, że to pytanie dotyczy Javy, ale myślę, że będzie dotyczyć większości języków programowania.

Author: Graham Borland, 2015-02-12

12 answers

W języku Java, a - b nigdy nie jest równe 0 if a != b. Dzieje się tak dlatego, że Java wymaga operacji zmiennoprzecinkowych IEEE 754, które obsługują liczby denormalizowane. Z spec :

W szczególności, język programowania Java wymaga obsługi denormalizowanych liczb zmiennoprzecinkowych IEEE 754 i stopniowego zaniku, co ułatwia udowodnienie pożądanych właściwości poszczególnych algorytmów numerycznych. Operacje zmiennoprzecinkowe nie "spłukują się do zera", jeśli obliczony wynik jest liczba denormalizowana.

Jeśli FPU działa z liczbami denormalizowanymi, odejmowanie liczb nierównych nigdy nie może dać zera (w przeciwieństwie do mnożenia), Zobacz też to pytanie.

Dla innych języków, to zależy. Na przykład w C lub c++ Obsługa IEEE 754 jest opcjonalna.

To powiedziawszy, możliwe jest przepełnienie wyrażenia 2 / (a - b), na przykład za pomocą a = 5e-308 i b = 4e-308.

 131
Author: nwellnhof,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:00

Jako obejście, co z poniższym?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}
W ten sposób nie zależy ci na obsłudze IEEE w żadnym języku.
 51
Author: malarres,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 15:38:44

Nie otrzymałbyś dzielenia przez zero niezależnie od wartości a - b, ponieważ dzielenie zmiennoprzecinkowe przez 0 nie powoduje wystąpienia wyjątku. Zwraca nieskończoność.

Teraz, jedynym sposobem a == b zwróci true jest to, czy a i b zawierają dokładnie te same bity. Jeśli różnią się tylko o najmniej znaczący bit, różnica między nimi nie będzie równa 0.

EDIT:

Jak słusznie skomentował Batszeba, są pewne wyjątki:

  1. "Not a number porównuje "false" z samym sobą, ale będzie miał identyczne wzorce bitowe.

  2. -0.0 jest zdefiniowana w celu porównania true Z + 0.0, a ich wzorce bitowe są różne.

Więc jeśli oba a i bDouble.NaN, osiągniesz klauzulę else, ale ponieważ NaN - NaN również zwróci NaN, nie podzielisz się przez zero.

 25
Author: Eran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-12 10:12:42

Nie Ma przypadku, w którym podział przez zero może się tutaj zdarzyć.

The SMT Solver z3 obsługuje precyzyjną arytmetykę zmiennoprzecinkową IEEE. Poprośmy Z3, aby znalazł liczby a i b takie, że 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)

Wynikiem jest UNSAT. Nie ma takich liczb.

Powyższy ciąg SMTLIB pozwala również Z3 wybrać dowolny tryb zaokrąglania (rm). Oznacza to, że wynik dotyczy wszystkich możliwych trybów zaokrąglania (z których jest pięć). Na wynik zawiera również możliwość, że dowolna ze zmiennych w grze może być NaN lub nieskończoność.

a == b jest zaimplementowana jako jakość fp.eq, tak aby +0f i -0f były równe. Porównanie z zero jest zaimplementowane przy użyciu fp.eq. Ponieważ pytanie ma na celu uniknięcie podziału przez zero, jest to odpowiednie porównanie.

Jeśli test równości został zaimplementowany przy użyciu równości bitowej, +0f i -0f byłoby sposobem na a - b zero. Niepoprawny poprzednia wersja tej odpowiedzi zawiera szczegóły dotyczące tej sprawy dla ciekawskich.

Z3 Online nie obsługuje jeszcze teorii FPA. Wynik ten został uzyskany przy użyciu najnowszej gałęzi unstable. Można go odtworzyć za pomocą wiązań. NET w następujący sposób:

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);

Używanie Z3 do odpowiadania na pytania IEEE float jest miłe, ponieważ trudno przeoczyć przypadki (takie jakNaN, -0f, +-inf) i możesz zadawać dowolne pytania. Nie trzeba interpretować i cytować specyfikacji. Ty może nawet zadawać Mieszane pytania typu float i integer, takie jak " Czy ten konkretny algorytm int log2(float) jest poprawny?".

 17
Author: usr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-15 10:41:32

Dostarczona funkcja może rzeczywiście zwrócić nieskończoność:

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

Wyjście to Result: -Infinity.

Gdy wynik podziału jest duży, aby zapisać go w dwójnasób, zwracana jest nieskończoność, nawet jeśli mianownik jest niezerowy.

 12
Author: D Krueger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 15:39:01

W implementacji zmiennoprzecinkowej zgodnej z IEEE-754, każdy typ zmiennoprzecinkowy może przechowywać liczby w dwóch formatach. Jedna ("znormalizowana") jest używana dla większości wartości zmiennoprzecinkowych, ale druga najmniejsza liczba, którą może reprezentować, jest tylko trochę większa od najmniejszej, a więc różnica między nimi nie jest reprezentowalna w tym samym formacie. Drugi ("denormalizowany") format jest używany tylko dla bardzo małych liczb, które nie są reprezentowalne w pierwszym formacie.

Obwody efektywna obsługa denormalizowanego formatu zmiennoprzecinkowego jest kosztowna i nie wszystkie procesory go zawierają. Niektóre procesory oferują wybór pomiędzy operacjami na naprawdę małych liczbach, które są dużo wolniejsze niż operacje na innych wartościach, lub gdy procesor po prostu traktuje liczby, które są zbyt małe dla znormalizowanego formatu jako zero.

Specyfikacje Javy sugerują, że implementacje powinny obsługiwać format denormalized, nawet na maszynach, na których robiłoby to kod działa wolniej. Z drugiej strony, jest możliwe, że niektóre implementacje mogą oferować opcje pozwalające kodowi działać szybciej w zamian za nieco niechlujne obchodzenie się z wartościami, które w większości przypadków byłyby zbyt małe, aby mieć znaczenie (w przypadkach, gdy wartości są zbyt małe, aby mieć znaczenie, może to być denerwujące, gdy obliczenia z nimi trwają dziesięć razy dłużej niż obliczenia, które mają znaczenie, więc w wielu praktycznych sytuacjach wyrównanie do zera jest bardziej przydatne niż powolna, ale dokładna arytmetyka).

 6
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-12 17:04:09

W dawnych czasach przed IEEE 754 było całkiem możliwe, że a != b nie oznacza a-b != 0 i odwrotnie. Był to jeden z powodów stworzenia IEEE 754.

Z IEEE 754 jest toprawie gwarantowane. Kompilatory C lub C++ mogą wykonywać operacje z większą precyzją niż jest to konieczne. Więc jeśli a i b nie są zmiennymi, ale wyrażeniami, to (a + b) != c nie oznacza (a + b) - c != 0, ponieważ a + b można obliczyć raz z większą precyzją, a raz bez większej precyzji.

Wiele FPU można przełączyć na tryb, w którym nie zwracają denormalizowanych liczb, ale zastępują je przez 0. W tym trybie, Jeśli a i b są małymi liczbami znormalizowanymi, gdzie różnica jest mniejsza od najmniejszej liczby znormalizowanej, ale większa od 0, to a != b również nie gwarantuje A = = b.

"nigdy nie porównuj liczb zmiennoprzecinkowych" to program kultowy. Wśród ludzi, którzy mają mantrę "potrzebujesz epsilonu", większość nie ma pojęcia, jak to wybrać epsilon prawidłowo.

 5
Author: gnasher729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-14 19:14:11

Nigdy nie powinieneś porównywać float lub Double dla równości; ponieważ, tak naprawdę nie możesz zagwarantować, że liczba przypisana do float lub double jest dokładna.

Aby porównać pływaki dla równości, musisz sprawdzić, czy wartość jest "wystarczająco blisko" do tej samej wartości:

if ((first >= second - error) || (first <= second + error)
 2
Author: aviad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-12 10:00:00

Myślę o przypadku, w którym Ty mógłbyś BYĆ w stanie to spowodować. Oto analogiczna Próbka w bazie 10-tak naprawdę, to by się stało w bazie 2, oczywiście.

Liczby zmiennoprzecinkowe są przechowywane mniej więcej w notacji naukowej - to znaczy, zamiast widzieć 35.2, zapisywana liczba byłaby bardziej podobna do 3. 52e2.

Wyobraźmy sobie dla wygody, że mamy jednostkę zmiennoprzecinkową, która działa w bazie 10 i ma 3 cyfry dokładności. Co się dzieje po odjęciu 9,99 od 10,0?

1.00e2-9. 99e1

Przesunięcie, aby dać każdej wartości ten sam wykładnik

1. 00e2-0. 999e2

Zaokrąglenie do 3 cyfr

1. 00e2-1. 00e2

Uh oh!

To, czy tak się stanie, zależy ostatecznie od projektu FPU. Ponieważ zakres wykładników dla podwójnego jest bardzo duży, sprzęt musi zaokrąglać wewnętrznie w pewnym momencie, ale w powyższym przypadku tylko 1 dodatkowa cyfra wewnętrznie zapobiegnie żadnemu problemowi.

 2
Author: Keldor314,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-12 14:34:31

Na podstawie odpowiedzi @ malarres i komentarza @Taemyr, oto mój mały wkład:

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

Chodzi mi o to, aby powiedzieć: najprostszym sposobem, aby dowiedzieć się, czy wynikiem podziału jest nan lub inf, jest faktycznie przeprowadzenie podziału.

 2
Author: Orace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 15:40:02

Podział przez zero jest niezdefiniowany, ponieważ granica od liczb dodatnich ma tendencję do nieskończoności, ograniczona od liczb ujemnych ma tendencję do nieskończoności.

Nie jestem pewien, czy jest to C++ czy Java, ponieważ nie ma znacznika języka.

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

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}
 1
Author: Khaled.K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-18 04:48:44

Podstawowy problem polega na tym, że komputerowa reprezentacja liczby podwójnej (inaczej float, lub liczby rzeczywistej w języku matematycznym) jest błędna, gdy masz" za dużo " dziesiętnych, na przykład gdy masz do czynienia z liczbą podwójną, której nie można zapisać jako wartość liczbową (pi lub wynik 1/3).

Więc a = = b nie można zrobić z jakąkolwiek podwójną wartością a i b, jak radzić sobie z a = = b, gdy a = 0,333 i b=1/3 ? W zależności od Twojego systemu operacyjnego vs FPU vs liczba vs język versus Liczba 3 Po 0, będziesz miał prawda lub FAŁSZ.

W każdym razie, jeśli wykonujesz "obliczenia podwójnej wartości" na komputerze, musisz poradzić sobie z dokładnością, więc zamiast wykonywać a==b, musisz wykonać absolute_value(a-b)<epsilon, A epsilon jest względny do tego, co modelujesz w tym czasie w swoim algorytmie. Nie możesz mieć wartości epsilon dla wszystkich podwójnych porównań.

W skrócie, kiedy wpisujesz a= = b, masz matematyczne wyrażenie, którego nie można przetłumaczyć na komputerze (dla dowolnej liczby zmiennoprzecinkowej).

[[2]] PS: hum, wszystko co tu odpowiem jest jeszcze mniej więcej w innych odpowiedziach i komentarzach.
 1
Author: Jean Davy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-18 20:11:59