Niedeterministyczna suma liczb zmiennoprzecinkowych

10

Pozwól mi powiedzieć oczywistą pięść: całkowicie rozumiem, że typy zmiennoprzecinkowe nie mogą dokładnie reprezentować wartości dziesiętnych . Tu nie chodzi o to! Niemniej jednak obliczenia zmiennoprzecinkowe mają być deterministyczne .

Teraz, gdy to już nie przeszkadza, pokażę ci ciekawy przypadek, który obserwowałem dzisiaj. Mam listę wartości zmiennoprzecinkowych i chcę je podsumować:

CREATE TABLE #someFloats (val float);
INSERT INTO #someFloats (val) VALUES (1), (1), (1.2), (1.2), (1.2), (3), (5);

SELECT STR(SUM(#someFloats.val), 30, 15) FROM #someFloats;

DROP TABLE #someFloats;

-- yields:
--   13.600000000000001

Jak dotąd tak dobrze - nie ma tu niespodzianek. Wszyscy wiemy, że 1.2nie można tego dokładnie przedstawić w reprezentacji binarnej, więc oczekuje się „nieprecyzyjnego” wyniku.

Teraz, gdy opuszczam inny stolik, dzieje się następująca dziwna rzecz:

CREATE TABLE #A (a int);
INSERT INTO #A (a) VALUES (1), (2);

CREATE TABLE #someFloats (val float);
INSERT INTO #someFloats (val) VALUES (1), (1), (1.2), (1.2), (1.2), (3), (5);

SELECT #A.a, STR(SUM(#someFloats.val), 30, 15)
  FROM #someFloats LEFT JOIN #A ON 1 = 1
 GROUP BY #A.a;

DROP TABLE #someFloats;
DROP TABLE #A;

-- yields
--   1   13.600000000000001
--   2   13.599999999999998

( skrzypce sql , możesz tam również zobaczyć plan wykonania)

Mam tę samą sumę dla tych samych wartości, ale inny błąd zmiennoprzecinkowy. Jeśli dodam więcej wierszy do tabeli #A, możemy zobaczyć, że wartość zmienia się między tymi dwiema wartościami. Byłem w stanie odtworzyć ten problem tylko z LEFT JOIN; INNER JOINdziała zgodnie z oczekiwaniami tutaj.

Jest to niewygodne, ponieważ oznacza to, że DISTINCT, GROUP BYczy PIVOTwidzi je jako różne wartości (co jest faktycznie jak odkryliśmy ten problem).

Oczywistym rozwiązaniem jest zaokrąglenie wartości, ale jestem ciekawy: czy istnieje logiczne wytłumaczenie tego zachowania?

Heinzi
źródło

Odpowiedzi:

15

W rzeczywistości link, do którego się odwołujesz, nie mówi, że obliczenia arytmetyczne zmiennoprzecinkowe są zawsze deterministyczne. W rzeczywistości w jednej z odpowiedzi wspomniano, że dodawanie nie jest skojarzone (znaczenie (a + b) + cniekoniecznie jest równe a + (b + c)), co również jest powiedziane w tej odpowiedzi .

Jeśli agregacja strumieniowa zdarzy się, aby przetworzyć wiersze każdej grupy w innej kolejności - co SQL Server jest zwykle wolny; jeśli nie ma ORDER BYodpowiedniej klauzuli, optymalizator wybierze to, co skanowanie lub wyszukiwanie lub inny operator zapytań będzie najszybszy, niezależnie od tego, w jakiej kolejności dokonuje dodania - to może wyjaśnić obserwowane zachowanie.

Dodawanie jest zawsze deterministyczne: umieszczasz te same dwa zmiennoprzecinkowe, uzyskujesz taki sam zmiennoprzecinkowy. Ale dodanie pływaków razem w innej kolejności może dać inny wynik.

Ross Presser
źródło
Asocjatywność nie ma związku z determinizmem, więc bit wprowadza w błąd.
Mooing Duck
Brak asocjatywności dodawania zmiennoprzecinkowego prowadzi do niedeterministycznego zachowania funkcji agregującej programu SQL Server SUM(), czy zgodziłby się @MooingDuck?
mustaccio
Nie? Podział liczb całkowitych jest wyraźnym kontrprzykładem. Jest niepowiązany, ale całkowicie deterministyczny. Podobnie podział zmiennoprzecinkowy powinien być niesocjacyjny i nadal deterministyczny. Z tego wynika, że ​​uzasadnione jest, aby dodawanie było niepowiązane i nadal deterministyczne. To powiedziawszy, jeśli kolejność dodawania nie jest deterministyczna, wynik również nie będzie deterministyczny, więc twoje pierwsze i ostatnie zdanie są nadal poprawne niezależnie od tego.
Mooing Duck
Podział liczb całkowitych jest kontrprzykładem dla SQL Servera SUM()nad argumentami zmiennoprzecinkowymi, jak dokładnie?
mustaccio
1
Podział liczb całkowitych jest niepowiązany i deterministyczny. Dlatego skojarzenie operacji arytmetycznych nie jest związane z determinizmem. Dlatego wszelki brak asocjatywności SUM()musi być nieistotny dla jego determinizmu. Zgadzam się, że SUMwydaje się to niedeterministyczne, ale powinieneś usunąć wzmianki o stowarzyszeniu, ponieważ jest to niepowiązane.
Mooing Duck