越位调用回合函数时的结果

时间:2018-11-09 21:49:31

标签: sql-server tsql sql-server-2008

我将两个floats除以100,然后乘以100。然后返回一个百分比。

我的问题是:当减法的右边部分返回2位数的浮点数时,为什么最终结果是没有四舍五入的浮点数?

这些是一个序列:

/* 1 */ 
-- Returns 0.956521739130435, which is correct.
select cast(198 as float)/(cast(198 as float) + cast(9 as float)) -- correct

/* 2 */ 
-- Returns 95.6521739130435, which is correct.
select 100*(cast(198 as float)/(cast(198 as float) + cast(9 as float))) --correct

/* 3 */ 
-- It's the same as previous one, but with a ROUND
-- Returns 95.65, which is correct.
select round(100*(cast(198 as float)/(cast(198 as float) + cast(9 as float))),2)

/* 4 */
-- Returns 4.34999999999999, should be 100-95.65, but it's not. ROUND is ignored. Why?
select 100-round(100*(cast(198 as float)/(cast(198 as float) + cast(9 as float))),2)
           |-------------- This returns 95.65 --------------------------------|

另一个序列:

/* 1 */ 
-- Returns 0.956521739130435, which is correct.
select cast(198 as float)/(cast(198 as float) + cast(9 as float))

/* 2 */ 
-- Returns 0.9565, which is correct.
select round(cast(198 as float)/(cast(198 as float) + cast(9 as float)), 4)

/* 3 */ 
-- Returns 95.65, which is correct.
select 100*round(cast(198 as float)/(cast(198 as float) + cast(9 as float)), 4)

/* 4 */
-- Returns 4.34999999999999, should be 100-95.65, but it's not. ROUND is ignored. Why?
select 100-(100*round(cast(198 as float)/(cast(198 as float) + cast(9 as float)), 4))
           |-------------------- This returns 95.65 --------------------------------|

我很好奇为什么会发生这种情况,尽管可以很容易在开始时用一个ROUND来解决:

select round(100-(100*(cast(198 as float)/(cast(198 as float) + cast(9 as float)))), 2)

我问的原因是因为它不容易复制。我尝试重现它,在2,000次中,它只发生了12次。小于1%,但第二个小数点后的浮点数带有重复数字(即3.47999999999),这很有意义:

declare @rand int = 1
While(@rand <= 2000)
begin
    select 100-round(100*(cast(abs(checksum(NewId()) % 1500) as float)/(cast(abs(checksum(NewId()) % 1500) as float) + cast(abs(checksum(NewId()) % 1500) as float))),2)
    set @rand = @rand + 1
end

我猜我的另一个问题是:使用select round(100*(cast(198 as float)/(cast(198 as float) + cast(9 as float))),2)返回 95.65 时,sql编辑器返回什么类型?

1 个答案:

答案 0 :(得分:0)

扩展Jeroen的评论:

SQL Server的FLOAT类型是双精度浮点值。与(大多数)浮点格式一样,该值以二进制形式存储。正如数字1/3不能用小数点后的有限位数来表示一样,数字95.65也不能用有限的位数来表示。可以存储在FLOAT中的最接近95.65的值具有确切值:

95.650000000000005684341886080801486968994140625

如果从100中减去该数字,您将得到:

4.349999999999994315658113919198513031005859375

显示时,此数字四舍五入为15位有效数字,并且打印的值是:

4.34999999999999

如前所述,您可以使用DECIMAL类型而不是FLOAT来解决此问题。

如果您想了解有关浮点数学的更多信息,StackOverflow和其他地方有很多可用资源。

-编辑-

我将使用括号表示法来重复小数。当我写

0.(3)

这意味着

0.333333333333333333333333333...等等。

让我们从头开始。 168可以存储在float中。 168+9177。可以将其存储在float中。如果将168除以177,则数学上正确的答案是:

0.95(6521739130434782608695)

但是该值不能存储在float中。可以存储在浮点数中的最接近的值为:

0.9565217391304348115710354250040836632251739501953125

采用该数字并乘以100,数学上正确的答案是:

95.65217391304348115710354250040836632251739501953125

由于您将float乘以100,所以得到了float,并且该数字无法存储在float中,因此最可能的值是:< / p>

95.6521739130434838216388016007840633392333984375

您要求将此float舍入到小数点后两位。数学上正确的答案是:

95.65

但是由于您要求对float进行四舍五入,因此答案也是float,并且该值无法存储在float中。可能的最接近值为:

95.650000000000005684341886080801486968994140625

您要求从100中减去。数学上正确的值是:

4.349999999999994315658113919198513031005859375

实际上,该值 可以存储在float中。这就是被选择的值。

将此数字转换为字符串时,SQL Server将结果四舍五入为15个有效数字。因此,该数字在打印时显示为:

4.34999999999999

当您在Java控制台上运行相同的计算时,将执行完全相同的计算,但是当打印该值时,Java将四舍五入为16个有效数字:

4.349999999999994

-另一个编辑-

为什么96.65不能完全存储在float中? float类型以二进制格式存储数字。如果要以二进制形式表示96.65,则数学上的精确值为:

1011111.1010011001100110011001100110011001100110011001(1001)

您可以看到图案。正如1/3表示为以十进制表示的无限重复值一样,此值也具有以二进制表示的无限重复值。您可以看到模式(1001)一遍又一遍地重复。

float只能容纳53个有效位。因此,这四舍五入为:

1011111.1010011001100110011001100110011001100110011010

如果将该数字转换回十进制,则会得到准确的值:

95.650000000000005684341886080801486968994140625

-另一个编辑-

您问当再次对结果调用Round时会发生什么情况。

我们以数字开头:

4.349999999999994315658113919198513031005859375

您要求将它四舍五入为2位。数学上正确的答案是:

4.35

由于要对浮点数进行四舍五入,因此此结果也必须是浮点数。用二进制表示该值。数学上正确的答案是:

100.0101100110011001100110011001100110011001100110011001(1001)

同样,这是一个重复的二进制值。但是float不能存储无限数量的位。该值四舍五入为53个有效位。结果是:

100.0101100110011001100110011001100110011001100110011

如果将其转换为十进制,则精确值为:

4.3499999999999996447286321199499070644378662109375

这是您选择的值。现在,SQL Server需要在屏幕上打印出来。和以前一样,四舍五入到15位有效数字。结果是:

4.35000000000000

它将删除结尾的零,并且您在屏幕上看到的结果是:

4.35

最后一轮没有做任何魔术。答案仍存储为浮点数,并且答案仍然不是精确值。发生这种情况时,SQL Server选择在打印浮点数时将值舍入到15个有效数字。在这种情况下,四舍五入的值恰好与您期望的确切值匹配。

如果在打印时将值四舍五入到14位,则原始查询似乎具有您期望的值。

如果将值四舍五入到16位,则最后一轮的结果将显示为

4.3499999999999996