Articles

niebezpieczeństwa związane z używaniem Float lub rzeczywistych typów danych

istnieje stary żart o arytmetyce zmiennoprzecinkowej:

„Jeśli pokroję ciasto na trzy, każdy kawałek to 0,33 ciasta. Jeśli skleję wszystkie trzy kawałki z powrotem, to daje mi 0,99 ciasta. Gdzie jest reszta tortu?”
— ” proste.

arytmetyka zmiennoprzecinkowa polega na tolerowaniu i zarządzaniu przybliżeniem, aby uniknąć błędów przepełnienia w obliczeniach. W realnym świecie zwykle zależy nam na precyzji w liczbach i zamiast tego poświęcimy przestrzeń i zasoby, aby uniknąć przepełnienia.

podczas gdy nauka działa szczęśliwie w granicach błędu, precyzja ma znaczenie w rachunkowości biznesowej. Kiedy byłem programistą cub, napisałem kiedyś coś, co uważałem za idealny sposób obliczania zysku z transakcji maklerskich. W milionie funtów, było to co najwyżej grosz lub dwa. Byłem zadowolony. Wykorzystano w nim obliczenia związane z kompilatorem PL/1, których wówczas używaliśmy do tworzenia pakietów finansowych. Pokazałem im precyzyjnie wykonaną aplikację i byli przerażeni. Grosz na milion funtów wydawał się przedsiębiorcom hardboiled city lekkomyślny. Nie mieliby go. Zostałem zmuszony do napisania pakietu binary-coded-decimal (BCD) w kodzie asemblera, który był dokładnie dokładny.

monit SQL ma regułę analizy kodu (BP023), która ostrzega o użyciu typów danychFLOATlubREAL, ze względu na znaczące nieścisłości, które mogą wprowadzić do rodzaju obliczeń, które wiele organizacji będzie rutynowo wykonywać na swoich danych serwera SQL.

przybliżone typy danych liczbowych

arytmetyka zmiennoprzecinkowa została opracowana w czasie, gdy priorytetem było oszczędzanie pamięci, dając jednocześnie wszechstronny sposób wykonywania obliczeń obejmujących duże liczby. Chociaż jest on nadal przydatny dla wielu rodzajów obliczeń naukowych, szczególnie tych, które są zgodne ze standardem podwójnej precyzji IEEE 754 dla arytmetyki zmiennoprzecinkowej, jest to z konieczności kompromis. Wskazówka znajduje się w nazwie tego typu danych i arytmetyki: „przybliżona”. Liczby zmiennoprzecinkowe nie mogą dokładnie reprezentować wszystkich liczb rzeczywistych: dodatkowo operacje zmiennoprzecinkowe nie mogą dokładnie reprezentować wszystkich operacji arytmetycznych. Jednak zakres wielkości liczby, którą mogą utrzymać, jest znacznie większy niż jest to możliwe w innych typach liczb, nawet jeśli nie zawsze jest dokładnie utrzymywana.

problemy, które wynikają z zastosowania obliczeń zmiennoprzecinkowych, wynikają z zaokrąglania podczas skomplikowanych obliczeń i są najczęściej widoczne, jeśli dane są „źle uwarunkowane”, tak że małe zmiany w danych wejściowych są powiększone w wyjściu. Nieścisłości są znacznie mniej widoczne przy zwiększonej precyzji reprezentacji liczb, ale mimo to są nadal obecne. Istnieją również pewne Ezoteryczne ograniczenia w użyciu liczb, które są ważne, ale nie mogą być reprezentowane w zmiennoprzecinkowym, takie jak Tan (π/2), ale mogą one ekscytować tylko matematyków.

typy danych zmiennoprzecinkowych serwera SQL

Standard SQL ma trzy zmienne, przybliżone typy danych, REALDOUBLEPRECISION I FLOAT(n)div>. SQL Server jest zgodny z tym wyjątkiem, że nie ma DOUBLEPRECISION typ danych, używając FLOAT(53) zamiast tego. Typy danychFLOAT(24) IFLOAT(53) odpowiadają Binary32 (pojedynczy) i Binary64 (Podwójny) w standardzie IEEE 754 i są przechowywane w 4 i 8 bajtach, odpowiednio 7 i 16 cyfr. Są one przydatne, gdy ważne jest, aby obliczenia przyniosły taki sam wynik jak aplikacja korzystająca z.NET framework, która również korzysta z IEEE 754. Typ podwójnej precyzji jest również wymagany, gdy liczby przekraczają w swojej wielkości maksimum dozwolone przezDECIMAL typ danych (38 cyfr), choć ze stratą precyzji. Przybliżone liczby nie mogą być oczywiście niezawodnie użyte w żadnym teście równości, takim jak klauzula WHERE.

obliczenia przy użyciu rzeczywistego typu danych (pojedyncza precyzja)

wypróbujęREAL typ danych. FLOAT(24) typ danych, lub mniejszy, reaguje w ten sam sposób. Pierwszą rzeczą do zapamiętania podczas eksperymentowania z liczbami zmiennoprzecinkowymi w SQL Server jest to, że SSMS renderuje liczbę zmiennoprzecinkową w sposób, który maskuje małe różnice. Na przykład:

1
SELECT Convert(REAL,0.100000001490116119384765625)

…daje 0.1

aby dokładniej zobaczyć, jaka wartość jest przechowywana w liczbie zmiennoprzecinkowej, musisz użyć funkcji str (), określającej dokładność, którą faktycznie chcesz.

1
2
3
4
5

/*jestem trochę niesprawiedliwy tutaj, ponieważ liczba dziesiętna 0.1 nie jest reprezentowana
w zmiennej zmiennoprzecinkowej; dokładna reprezentacja binarna miałaby ciąg „1100”
kontynuujący się bez końca:*/
declare @firstapproximate real = 0.1
select str(@firstapproximate,20,16)-powinno być 0.100000001490116119384765625

W końcu mamy do czynienia z danymi z milionami wierszy, więc małe błędy będą się kumulować, chyba że, jak „zaokrąglanie banków”, uśrednią się. Ten błąd jest już bliski „groszowi w milionie funtów” (1/240000000), o którym wspomniałem we wstępie!

unikajmy 0.1 i odkładajmy to do wariantu zmiennoprzecinkowego. Co powiesz na podzielenie 1 przez 3? Z pewnością to nie może być problem?

1
2
3
4
5

= 1
określając @dzielnik real =3
wybierz ul. (@wypłat /@dzielnik,20,16) jako czynnik
– produkuje 0.3333333333333

oj. Źle to zrozumiał. OK, to drobny błąd, ale pamiętaj o mojej historii o bankierach. Odpowiedź jest dobra lub zła, nie ma odcieni szarości dla mężczyzn w szarych garniturach. W szkole biznesu jest tylko kleszcz i krzyż. Brak znaku oznaczającego „wystarczająco blisko”.

prostym testem jest podzielenie jednego przez liczby od 1 do 20. Co może pójść nie tak?

możemy przechowywać wyniki obliczeń zmiennoprzecinkowych i liczbowych, zarówno przekonwertowane na ciągi, a następnie porównujemy ciągi (ostrzegamy, żeSTR() może umieścić w spacji wiodącej, co powoduje komplikację).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

DECLARE @divisor REAL, @dividend REAL = 1
DECLARE @FloatingPointCalculations TABLE (Divisor INT, QuotientFloatingPoint VARCHAR(20), QuotientNumeric VARCHAR(20));
DECLARE @ii INT = 1;
DECLARE @iiMax INT = 20;
WHILE (@ii <= @iiMax)
BEGIN
SELECT @divisor = @ii;
INSERT INTO @FloatingPointCalculations (Divisor, QuotientFloatingPoint,
QuotientNumeric)
SELECT @ii AS divisor, Str(@Dividend / @divisor, 20, 16) AS QuotientFloatingPoint,
Convert(VARCHAR(20), 1.0000000 / @ii) AS QuotientNumeric;
SELECT @ii += 1;
END;
SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations AS The;

Now, what if we list the rows where the numbers don’t match?

1
2
3

SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations The
WHERE Left(LTrim(The.QuotientFloatingPoint),16)<> Left(LTrim(The.QuotientNumeric),16)

Tylko wtedy, gdy dzielnik był równy 1, 2, 4, 8 lub 16, wynik był poprawny.

w przypadku, gdy masz nadzieję, że w jakiś sposób float był dokładny, a wersja numeryczna nie, oto iloraz liczbowy obliczony w Excelu:

obliczenia przy użyciu FLOAT(25) lub over (double precision)

Jeśli używasz zmiennoprzecinkowego o podwójnej precyzji, FLOAT(25) lub over, testy są pomyślnie zakończone, ponieważ funkcja STR() pozwala na maksymalnie szesnaście miejsc na prawo od miejsca dziesiętnego. Jeśli jest ich więcej niż 16, wynik jest obcięty. Double precision datatype ma szesnaście cyfr, podczas gdy single precision datatype ma siedem. Zauważyłeś również, że pojedynczy typ danych precyzji pobiera pierwsze siedem cyfr w prawo. Podobnie, Podwójna precyzja dostaje pierwsze szesnaście cyfr prawo. Możemy po prostu rozszerzyć liczbę, aby zobaczyć przybliżenie.

1
2

DECLARE @FirstApproximate FLOAT(53) = 100000000000000.1
wybierz str(@firstapproximate,40,16) jako bignumberwithadecimal

ta część ułamkowa zniknęła, prawda? Jest to prawdopodobnie tylko niewielka różnica, ale w niektórych obliczeniach może powodować problemy.

wniosek

arytmetyka zmiennoprzecinkowa jest szybka i ekonomiczna w przechowywaniu, ale zapewnia przybliżony wynik. Nadaje się do dobrze uwarunkowanych zastosowań naukowych, ale nie do obliczeń finansowych, które wymagają, aby liczba była albo „dobra”, albo „zła”. Ma również dodatkową wadę w bazie danych, ponieważ nie można wiarygodnie i konsekwentnie testować dwóch przybliżonych liczb pod kątem równości.

nie jest poprawne stwierdzenie, że nigdy nie należy używać liczb zmiennoprzecinkowych w typach danych SQL lub w arytmetyce. Przybliżone typy są tam w standardzie SQL w celu. W dzisiejszych czasach zawsze trzymałbym się double precision zmiennoprzecinkowego typu danych w SQL Server, gdzie jest odpowiednie Wymaganie. Są świetne do takich celów, jak modelowanie systemów pogodowych lub kreślenie trajektorii, ale nie do rodzajów obliczeń, dla których przeciętna organizacja może korzystać z bazy danych.

Jeśli zauważysz błędne użycie tych typów, powinieneś przełączyć się na odpowiedni typDECIMALNUMERIC. Jeśli wiesz, że potrzebujesz arytmetyki zmiennoprzecinkowej i możesz wyjaśnić, dlaczego, to prawdopodobnie wiesz wystarczająco dużo, aby uniknąć pułapek zmiennoprzecinkowych, takich jak ta, która miała miejsce w słynnej awarii rakiety Patriot, która doprowadziła bezpośrednio do śmierci 28.