SQL 十进制格式在所有情况下都无法正常工作

SQL Decimal formatting not working properly in all cases

SQL 服务器小数功能未按预期工作。

为了使用示例数据进行测试,我创建了一个 table 并向其中插入了值。 然后,我尝试对这些值进行 运行 十进制函数。

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 个值的格式相同。但是,对于第三个值,它 returns 上限四舍五入值。

如果我们在第一个查询中扩展三个数字的精度,也许可以解释这一点:

16704.4050
20382.1349
2683.1351

将以上每一项四舍五入到小数点后两位,这是对 DECIMAL(10,2) 的强制转换,会产生:

16704.40
20382.13
2683.14

这个有用吗:

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

这是 SQLServer 2012 的演示:DEMO

第一个问题:为什么它们的值不同?
因为它们的类型不同,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

db<>fiddle here

或者你可以施放AS DECIMAL(8, 3)

SELECT CAST(VAL AS DECIMAL(8,3)) AS NEWVAL 
FROM T;

这是由于浮点数的性质是不精确的并且是二进制的。但我想演示这是如何工作的。

问题是无法准确表示小数,例如 0.135。作为浮点表示,它通常是这样的:

 0.134999999234243423

(请注意,这些数字以及此答案中所有值的表示都是虚构的。它们旨在具有代表性以说明这一点。)

9的数量其实更大。而后面的数字只是代表。在此表示中,我们不会看到截断值的问题。毕竟 0.1349999 应该舍入到与 0.13499.

相同的值

在二进制中,这看起来不同:

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

(注:数值是虚构的!)

即二进制表示的"infinite"部分不是一堆重复的1或者重复的0;它有一个模式。这类似于大多数以 10 为基数的数字的倒数。例如,1/7 有一个六位数的重复分量,142857。我们往往会忘记这一点,因为常见的倒数要么是精确的 (1/5 = 0.2),要么只有一个重复数字 (1/6 = 0.166666...)。 1/7 是第一个不那么简单的情况——几乎所有的小数都是这样。对于有理数,总是一个重复序列,不管基数如何,它永远不会长于被除数(底部的数字)减1)。

我们可以认为这是因为所有十进制表示(不管基数如何)总是有一些重复的数字。对于精确表示,重复部分是 0。对于其他人来说,它很少是一位数。通常,它是多个数字。描述这一点是一项有趣的数学练习。但重要的是重复部分有 1s 和 0s.

现在,发生了什么。一个浮点数由三部分组成:

  • 一个量级。这是代表指数的位数。
  • 整数部分,即小数点前的数字。
  • 整数部分,即小数点后的数字。

(实际上,最后两个是一个整数,但我发现将它们分成两个部分更容易解释这一点。)

两个整数部分只有固定数量的位数可用。这看起来像什么?代表性的模式是这样的:

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<>fiddle 放在一起,所以你可以看到舍入是如何围绕 2 的幂变化的。