Czy kiedykolwiek myślałeś, że SQL może być błędny w matematyce? Brzmi szalenie. Ale jeśli używałeś typu danych SQL FLOAT, być może napotkałeś problemy, które zamierzam ci pokazać.
Rozważ to. 0,1 + 0,2 powinno być 0,3, prawda? Ale sprawdź to za pomocą typu danych SQL FLOAT.
DECLARE @f1 FLOAT = 0.1
DECLARE @f2 FLOAT = 0.2
SELECT CASE WHEN @f1 + @f2 = .3 THEN 1 ELSE 0 END
Prawidłowy wynik to 1. Ale sprawdź Rysunek 1.
Czy mam teraz twoją uwagę? Mam nadzieję, że tak. To dość przerażające polegać na systemie, który nie da nam poprawnej matematyki. Ale ten artykuł pomoże ci tego uniknąć.
Jest trochę pracy do wykonania. Musimy zacząć od tego, o co chodzi w typie danych FLOAT.
Co to jest typ danych SQL FLOAT?
Typ danych SQL FLOAT to przybliżony numeryczny typ danych używany dla liczb zmiennoprzecinkowych. Mogą przechowywać bardzo duże lub bardzo małe liczby. Są one również używane do obliczeń, które wymagają szybkiego czasu przetwarzania.
Wszystko to odbywa się kosztem utraty precyzji. Co więcej, nie można powiedzieć, gdzie po obliczeniu zostanie umieszczony przecinek dziesiętny – unosi się . Tymczasem dokładne wartości liczbowe, takie jak DECIMAL, będą miały ustaloną pozycję przecinka dziesiętnego.
Jak deklarować typ danych SQL FLOAT
Składnia to FLOAT[(n)], gdzie n to liczba bitów używanych do przechowywania mantysy liczby zmiennoprzecinkowej w notacji naukowej. To również dyktuje precyzję i rozmiar przechowywania. Możliwe wartości n mieszczą się w zakresie od 1 do 53. Pamiętaj, że n jest opcjonalne.
Oto przykład:
DECLARE @floatValue1 FLOAT; -- Float variable without the number of bits
DECLARE @floatValue2 FLOAT(3) -- Float variable with 3 bits
Jeśli nie określisz n , wartość domyślna to 53. Jest to również wartość maksymalna. Ponadto FLOAT(53) to liczba zmiennoprzecinkowa podwójnej precyzji lub binarna64. Oprócz używania FLOAT(53), możesz również zadeklarować to jako PODWÓJNA PRECYZJA.
Poniższe 3 deklaracje są funkcjonalnie równoważne:
DECLARE @double1 FLOAT(53);
DECLARE @double2 FLOAT;
DECLARE @double3 DOUBLE PRECISION;
Tabela pokazuje liczbę bitów i odpowiedni rozmiar pamięci.
Wartość n | Rozmiar pamięci |
1 do 24 | 4 bajty |
25 do 53 | 8 bajtów |
Czy SQL FLOAT i REAL są takie same?
REAL to także FLOAT(24). Jest również określany jako pojedyncza precyzja lub binarny32.
Dlaczego wiedza o tym jest ważna
Wiedząc, że jest to przybliżona liczba, nie będziesz używać jej do obliczeń wymagających dokładności. Czy zajmujesz się również przechowywaniem i pamięcią? Użyj REAL lub FLOAT(24), jeśli nie potrzebujesz zbyt dużych lub zbyt małych wartości.
Jakie są różnice między FLOAT a DECIMAL?
FLOAT to przybliżona liczba. DECIMAL to dokładna liczba. Oto podsumowanie różnic w tabeli:
PŁYWAJĄCY | DZIESIĘTNY | |
Punkt dziesiętny | Może być umieszczony w dowolnym miejscu cyfry | Stała pozycja |
Maksymalny limit | 38 cyfr lub 99,999,999,999,999,999,999,999,999,999,999,999,999,999 | FLOAT(53) ma maksymalny zakres 1,79E+308 lub 179, po którym następuje 306 zer |
Pamięć | Maksymalnie 8 bajtów | Maksymalnie 17 bajtów |
Wynik obliczeń | W przybliżeniu | Dokładne |
Kontrole porównawcze | Nie używaj =lub <>. Unikaj podczas zaokrąglania | Operatory=lub <>. Dobre do zaokrąglania |
Widziałeś już na rysunku 1, jak obliczanie liczby FLOAT może mieć dziwne wyniki. Jeśli zmienisz typ danych na DECIMAL w ten sposób:
DECLARE @d1 DECIMAL(2,1) = 0.1
DECLARE @d2 DECIMAL(2,1) = 0.2
SELECT CASE WHEN @d1 + @d2 = 0.3 THEN 1 ELSE 0 END
Wynik będzie poprawny.
Problemem jest również użycie operatora nierówności. Sprawdź pętlę poniżej.
DECLARE @floatValue FLOAT(1) = 0.0
WHILE @floatValue <> 5.0
BEGIN
PRINT @floatValue;
SET @floatValue += 0.1;
END
Co myślisz? Zobacz Rysunek 2 poniżej.
Bum! Nieskończona pętla! Warunek nierówności zawsze będzie prawdziwy. Logicznym wyborem jest więc zmiana typu na DZIESIĘTNY.
DECLARE @decimalValue DECIMAL(2,1) = 0.0
WHILE @decimalValue <> 5.0
BEGIN
PRINT @decimalValue;
SET @decimalValue += 0.1;
END
Powyższy kod z pewnością zatrzyma się, gdy @decimalValue jest równy 5,0. Przekonaj się sam na Rysunku 3 poniżej.
Ładny! Ale jeśli nadal będziesz nalegać na FLOAT, będzie to działać dobrze bez nieskończonej pętli.
DECLARE @floatValue FLOAT(1) = 0.0
WHILE @floatValue < 5.0
BEGIN
PRINT @floatValue;
SET @floatValue += 0.1;
END
Tymczasem zaokrąglanie również się kończy. Rozważ następujące kwestie:
DECLARE @value FLOAT(2) = 1.15
SELECT ROUND(@value, 1) -- This will result to 1.1
Zamiast 1.20 kod daje wynik 1.1. Ale jeśli użyjesz DECIMAL, wynik będzie poprawny.
DECLARE @value DECIMAL(3,2) = 1.15
SELECT ROUND(@value, 1) -- This will result in 1.2 or 1.20
Kiedy FLOAT jest poprawne, a DECIMAL nie
Czy dokładne dane liczbowe NIE są tak dokładne przez cały czas? Aby odtworzyć ten problem, użyjemy obliczeń, a następnie je odwrócimy. Najpierw przygotujmy dane.
CREATE TABLE ExactNumerics1
(
fixed1 DECIMAL(8,4),
fixed2 DECIMAL(8,4),
fixed3 DECIMAL(8,4),
calcValue1 AS fixed3 / fixed1 * fixed2
)
GO
INSERT INTO ExactNumerics1
(fixed1,fixed2,fixed3)
VALUES
(54,0.03,1*54/0.03)
Powyższa tabela użyje stałych wartości dla pierwszych 2 kolumn. W trzeciej kolumnie będą obliczenia. Wreszcie czwarta kolumna, która jest kolumną obliczeniową, wykona obliczenia odwrotne. Prawidłowy wynik w obliczonej kolumnie powinien wynosić 1.
Teraz, aby porównać to z FLOAT, stwórzmy podobną tabelę i dane.
CREATE TABLE ApproxNumerics1
(
float1 FLOAT(2),
float2 FLOAT(2),
float3 FLOAT(2),
calcValue1 AS float3 / float1 * float2
)
INSERT INTO ApproxNumerics1
(float1, float2, float3)
VALUES
(54,0.03,1*54/0.03)
Zadajmy zapytanie.
SELECT * FROM ApproxNumerics1
SELECT * FROM ExactNumerics1
Wyniki? Sprawdź rysunek 4.
Co tu się stało? FLOAT zrobił to dobrze, ale DECIMAL nie. Coś poszło nie tak.
Niejawna konwersja CZY TO PONOWNIE
Niejawna konwersja ma miejsce, ponieważ SQL jest wyrozumiały. Gdy w obliczeniach używane są różne typy danych, SQL Server próbuje je przekonwertować za pomocą niejawnej konwersji za naszymi plecami.
Czy konwersja naprawdę miała miejsce? Poza tym każda kolumna w ExactNumerics1 tabela jest DZIESIĘTNA.
Sprawdźmy strukturę tabeli ExactNumerics1 tabela w SQL Server Management Studio:
Zwróć uwagę na obszar w czerwonej ramce na rysunku 3. Obliczona kolumna to DECIMAL(30,17), a nie DECIMAL(8,4). Według oficjalnej dokumentacji 2 kolumny DECIMAL o różnej precyzji i skali to 2 różne typy danych . Przekonaj się tutaj. Ze względu na różnicę wymagana jest konwersja. W grę wchodzi więc niejawna konwersja.
A jeśli są różne i nastąpiła niejawna konwersja?
Ponownie, na podstawie oficjalnej dokumentacji utrata precyzji lub skali może nastąpić podczas niejawnej konwersji . Dlatego wymagane jest wyraźne CAST. Zwróć uwagę na typ danych DECIMAL w tabeli konwersji w tym odnośniku.
Tu właśnie wydarzyła się jakaś strata. Jeśli obliczona kolumna ma również wartość DECIMAL(8,4), niejawna konwersja nie występuje.
Aby uniknąć niejawnej konwersji, postępuj zgodnie z oficjalną dokumentacją. Struktura tabeli powinna wyglądać tak:
CREATE TABLE ExactNumerics2
(
fixed1 DECIMAL(8,4),
fixed2 DECIMAL(8,4),
fixed3 DECIMAL(8,4),
calcValue1 AS CAST(fixed3 / fixed1 * fixed2 AS DECIMAL(8,4)) -- the explicit CAST
)
Wyraźne CAST w kolumnie obliczeniowej zapewnia spójność typów danych. Jeśli również zastosujemy się do tej struktury i wstawimy te same dane, wynik będzie poprawny. Sprawdź nowe dane wyjściowe na rysunku 6 poniżej.
Ostatecznie dokładna liczba nie będzie dokładna, jeśli niejawna konwersja nastąpi między 2 lub więcej wartościami DECIMAL.
Dlaczego wiedza o tym jest ważna
Daje wyobrażenie o tym, czego potrzebujesz dla swoich tabel i zmiennych. Co więcej, niejawna konwersja może sprawić, że nawet dokładna liczba będzie zwariowana. Dlatego wyraźnie zdefiniuj precyzję i skalę oraz bądź z nimi spójny w swoich obliczeniach.
Czy powinienem używać SQL FLOAT do danych finansowych?
Przy obliczaniu wartości procentowych w każdym kawałku wykresu kołowego suma powinna wynosić 100%. Sumy w podsumowaniu i raportach szczegółowych również powinny być spójne. Jeśli dokładność wyników ma kluczowe znaczenie, przybliżony typ danych, taki jak FLOAT, nie wystarczy. Logicznym wyborem jest tutaj wartość DZIESIĘTNA.
Pozostaje jednak pytanie.
Kiedy należy używać FLOAT?
Użyj FLOAT dla danych, które wymagają wartości astronomicznych, takich jak odległości między galaktykami. Tymczasem typ danych DECIMAL będzie narażony na przepełnienie arytmetyczne w przypadku tego typu danych. Drobne wartości, takie jak średnica jądra atomowego, również będą pasować przy użyciu FLOAT. Dane naukowe i inne wartości, które nie wymagają precyzji, mogą również skorzystać z FLOAT.
Dlaczego wiedza o tym jest ważna
Nie mówimy, że FLOAT jest zły, a DECIMAL jest dobry lub odwrotnie. Znajomość poprawnych przypadków użycia dla każdego z nich zapewni Tobie i Twoim użytkownikom zamierzone wyniki. I znowu chcesz, żeby Twoi użytkownicy byli zadowoleni, prawda?
Wniosek
Pod koniec dnia wszyscy chcemy wykonywać swoją pracę i być w niej dobrzy. Matematyka zawsze będzie częścią naszej pracy. Znajomość poprawnych liczbowych typów danych również pomoże nam sobie z tym poradzić. Nie jest to trudne, jeśli wiesz, co robisz.
Mam nadzieję, że ten artykuł pomógł ci uniknąć dziwnej matematyki w SQL Server.
Czy masz coś jeszcze do dodania? Następnie daj nam znać w sekcji Komentarze. Udostępnij to również na swoich ulubionych platformach społecznościowych.