Десятичное форматирование SQL не работает должным образом во всех случаях - PullRequest
1 голос
/ 02 ноября 2019

Десятичная функция SQL Server не работает должным образом.

Чтобы протестировать образцы данных, я создал таблицу и вставил в нее значения. Затем я попытался запустить десятичную функцию для этих значений.

CREATE TABLE TEST_VAL
(
   VAL float
)

SELECT * FROM TEST_VAL

Вывод:

VAL
----------
16704.405
20382.135
 2683.135

SELECT CAST(VAL AS DECIMAL(15, 2)) AS NEWVAL 
FROM TEST_VAL;

Вывод:

NEWVAL
-------------
16704.40
20382.13
 2683.14

Я ожидал одинакового форматирования для всех 3ценности. Но для третьего значения возвращается значение округления потолка.

Ответы [ 4 ]

1 голос
/ 02 ноября 2019

Это связано с тем, что числа с плавающей запятой неточны и находятся в двоичном виде . Но я хочу продемонстрировать, как это работает.

Проблема в том, что десятичное число, такое как 0,135, не может быть представлено точно. Что касается представления с плавающей запятой, оно обычно будет выглядеть примерно так:

 0.134999999234243423

(Обратите внимание, что эти числа, как и все представления значений в этом ответе, составлены. Они предназначены для того, чтобы представлять.)

Количество 9 с на самом деле больше. И последующие цифры являются просто представительными. В этом представлении мы не увидим проблемы с усечением значения. Ведь 0.1349999 должно округляться до того же значения, что и 0.13499.

В двоичном виде это выглядит иначе:

0.11101000010101  10011 10011 10011 10011 . . .
----------------  --------------
  ~0.135             "arbitrary" repeating pattern

(Примечание: значения составлены!)

То есть «бесконечная» часть двоичного представления не является группой повторяющихся 1 с или повторяющихся 0 с;у этого есть образец. Это аналогично обратному большинству чисел в базе 10. Например, 1/7 имеет повторяющийся компонент из шести цифр, 142857. Мы склонны забывать об этом, потому что общие инверсии либо точны (1/5 = 0,2), либо имеют одну повторяющуюся цифру (1/6 = 0,166666 ...). 1/7 - это первый случай, который не так прост - и почти все десятичные дроби такие. Для рациональных чисел существует всегда повторяющаяся последовательность независимо от основания, и она никогда не длиннее, чем делимое (число внизу) минус 1).

Мы можем думать об этом как обо всех десятичных числахпредставления (независимо от базы) всегда имеют некоторое количество повторяющихся цифр. Для точного представления повторяющаяся часть равна 0. Для других это редко одна цифра. Обычно это несколько цифр. И это забавное упражнение по математике, чтобы охарактеризовать это. Но все, что важно, это то, что повторяющаяся часть имеет 1 с и 0 с.

Теперь, что происходит. Число с плавающей запятой состоит из трех частей:

  • величина. Это число битов, представляющих показатель степени.
  • целочисленная часть, которая является числом до десятичной точки.
  • целочисленная часть, которая является числом после десятичной точки.

(На самом деле, последние два действительно одно целое число, но мне проще объяснить это, разделив их на два компонента.)

Доступно только фиксированное количество битдля двух целочисленных частей. Как это выглядит? Еще раз репрезентативные шаблоны выглядят примерно так:

0.135      0  11101000010101100111001110
1.135      1  11101000010101100111001110
2.135      10  1110100001010110011100111
4.135      100  111010000101011001110011
8.135      1000  11101000010101100111001
16.136     10000  1110100001010110011100
-----------^ part before the decimal
------------------^ part after the decimal

Примечание: это исключает часть величины десятичного представления.

Как видите, цифры отсекаются отконец. Но иногда отрубается 0 - поэтому в представляемом значении нет изменений. И иногда это 1. И есть изменение.

С этим вы можете увидеть, как значения существенно колеблются, скажем:

0.135 --> 0.135000000004
1.135  --> 0.135000000004
2.135  --> 0.135000000004
4.135  --> 0.135000000001
8.135  --> 0.135999999997
16.135 --> 0.135999999994

Затем они округляются по-разному, что вы и видите.

Я собрал эту маленькую db <> скрипку , чтобы вы могли видеть, как округление изменяется вокруг степеней двух.

1 голос
/ 02 ноября 2019

Возможно, это можно объяснить, если мы увеличим точность трех чисел в первом запросе:

16704.4050
20382.1349
2683.1351

Округление каждого из перечисленных выше только до двух десятичных знаков, что является приведением к DECIMAL(10,2) сделает, даст:

16704.40
20382.13
2683.14
0 голосов
/ 02 ноября 2019

первый вопрос: почему они не имеют одно и то же значение?
, поскольку их тип отличается, CAST(VAL as decimal(4,2)) будет форматироваться как ##.##, а не ##.###, поэтому в вашем случае он получит максимальное значение.

Почему бы не использовать тот же тип?

 CREATE TABLE T
 (
      [VAL] DECIMAL(8,3)
 );

 INSERT INTO T ([VAL])
 VALUES (16704.405), (20382.135), (2683.135);

 SELECT * FROM T

Вывод:

VAL
-----------
 16704.405
 20382.135
  2683.135

дБ <> скрипка здесь

или вы можете разыграть AS DECIMAL(8, 3)

SELECT CAST(VAL AS DECIMAL(8,3)) AS NEWVAL 
FROM T;
0 голосов
/ 02 ноября 2019

Будет ли это полезно:

select CONVERT(DECIMAL(15,2), ROUND(VAL, 2, 1)) AS NEWVAL 
from TEST_VAL;

Вот DEMO для SQLServer 2012: DEMO

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...