Operacja dziesiętna programu SQL Server nie jest dokładna

Oct 27 2020

Kiedy wykonuję tę prostą operację na serwerze SQL:

Select 800.0 /30.0 

Otrzymuję wartość 26,666666, gdzie nawet jeśli zaokrągla 6 cyfr, to powinno wynosić 26,666667. Jak mogę uzyskać dokładne obliczenia? Próbowałem poszukać tego w Internecie i znalazłem rozwiązanie, w którym przed operacją rzucam każdy operand na ułamek dziesiętny o wysokiej dokładności, ale nie będzie to dla mnie wygodne, ponieważ mam wiele długich, złożonych obliczeń. myślę, że musi być lepsze rozwiązanie.

Odpowiedzi

2 Larnu Oct 26 2020 at 23:46

W przypadku dzielenia przy użyciu programu SQL Server wszystkie cyfry występujące po wynikowej skali są obcinane, a nie zaokrąglane. Dla twojego wyrażenia masz a decimal(4,1)i a decimal(3,1), co daje decimal(10,6):

Precision = p1 - s1 + s2 + max(6, s1 + p2 + 1)
Scale = max(6, s1 + p2 + 1)

W rezultacie 26.66666666666666~jest obcięty do 26.666666.

Możesz to obejść, zwiększając dokładność i skalę, a następnie z CONVERTpowrotem do wymaganej precyzji i skali. Na przykład zwiększ precyzję i skalę operacji decimal(3,1)to decimal(5,2)i przekonwertuj z powrotem na decimal(10,6):

SELECT CONVERT(decimal(10,6),800.0 / CONVERT(decimal(5,3),30.0));

To wraca 26.666667.

CR241 Oct 26 2020 at 23:50

Może to być pomocne:

Użyj ROUND (Transact-SQL)

SELECT ROUND(800.0 /30.0, 5) AS RoundValue;

Wynik:

RoundValue

26.666670
seanb Oct 27 2020 at 08:41

Uważam, że dzieje się tak dlatego, że SQL Server przyjmuje liczby jako decimalwartości (które są dokładne, np. 6,6666 i 6,6667 oznaczają dokładnie te wartości, a nie 6 i dwie trzecie), a nie floatwartości (które mogą działać z przybliżonymi liczbami).

Jeśli na początku rzucasz / konwertujesz go wprost na a float, obliczenia powinny działać płynnie.

Oto kilka przykładów, aby wykazać różnicę między int, decimali floatobliczeń

  • Dzielenie 20 przez 3
  • Dzieląc 20 przez 3, a następnie mnożąc ponownie przez 3 (co matematycznie powinno wynosić 20).
SELECT  (20/3)                             AS int_calc,
        (20/3) * 3                         AS int_calc_x3,
        (CAST(20 AS decimal(10,3)) /3)     AS dec_calc,
        (CAST(20 AS decimal(10,3)) /3) * 3 AS dec_calc_x3,
        (CAST(20 AS float) /3)             AS float_calc,
        (CAST(20 AS float) /3) * 3         AS float_calc_x3

z następującymi wynikami

int_calc   int_calc_x3   dec_calc   dec_calc_x3   float_calc         float_calc_x3
6         18             6.666666   19.999998     6.66666666666667   20

W twoim przypadku możesz użyć

Select CAST(800.0 AS float) /30.0

co daje 26,6666666666667

Zwróć uwagę, że jeśli następnie pomnożymy przez 30, otrzyma poprawny wynik, np.

Select (CAST(800.0 AS float) /30.0) * 30

daje 800. Rozwiązania zajmujące się liczbami dziesiętnymi nie będą miały tego.

Zauważ również, że kiedy już masz to jako zmiennoprzecinkowe, to powinno pozostać zmiennoprzecinkowe, dopóki nie zostanie w jakiś sposób zamienione z powrotem na ułamek dziesiętny lub int (np. Zapisane w tabeli jako int). Więc...

SELECT A.Num / 30
FROM (Select ((CAST(800.0 AS float) /30.0) * 30) AS Num) AS A

nadal da 26.6666666666667

Miejmy nadzieję, że pomoże ci to w twoich długich, złożonych obliczeniach.