我正在尝试将一个微妙的计算从C#.NET迁移到TSQL,并且绝对每个结果必须保持不变,无论原始C#.NET答案在某些情况下是否不准确。
我的TSQL脚本大部分都是成功的,然而,在.NET中有一些我不能捕获的异常舍入行为。我将用两个例子来证明:
这是我正在尝试重新创建的.NET代码。
(double)Math.Round(number, 1, MidpointRounding.AwayFromZero );
double number = 6.1499999999999995; // returns 6.1
double number = 6.9499999999999993; // returns 7 (which, to me, seems inconsistent)
这是我尝试与上面的.NET代码保持一致的TSQL代码。
SELECT ROUND(ROUND(CAST(@Score as decimal(10,1)), 16), 1)
DECLARE @Score DECIMAL(18,16) = 6.1499999999999995 -- returns 6.1
DECLARE @Score DECIMAL(18,16) = 6.9499999999999993 -- returns 6.9 (needs to be 7)
我的目标是让我的TSQL代码在第二种情况下返回7而不是6.9。有谁知道我怎么能做到这一点?
答案 0 :(得分:3)
double
是十进制数字。 double
不是float
。由于您的C#代码使用double
,因此只有在T-SQL中复制类似行为的实际方法是在T-SQL中使用二进制浮点数 - float(53)
(具体而言,decimal
}对应于double
)。舍入行为不是"不常见",它符合IEEE-754标准。
但是,如果您关心小数精度(看起来应该如此),您应该真正推动切换C#代码以使用LocalDateTime
而不是Interval
。当你想要小数精度时,二进制浮点是一个糟糕的选择。
答案 1 :(得分:0)
我认为没有一种可靠的方法可以使用普通的T-SQL获得你想要的东西。
你的逻辑错了:
在您应用ROUND 之前, CAST(@Score as decimal(10,1))
已经完成了
DECLARE @Score decimal(18,16) = 6.9499999999999993
SELECT @Score, CAST(@Score as decimal(10,1))
6.9499999999999993 6.9
您ROUND
到16位,然后将另一个ROUND
应用于1位数,但ROUND(ROUND(n, 16), 1)
可能与ROUND(@n,1)
的结果不同,绝对不是这样的.NET轮次。
另一个问题是舍入规则,您指定MidpointRounding.AwayFromZero
,但afaik T-SQL的默认值为Rounding to Even。
您可以查看SQL Server Rounding Methods并尝试实施 Round to Even ,但仍然存在有效位数问题,DEC(18,16)
有18,但是{{ 1}}只有16。
如果你不能在T-SQL中使用CLR功能或在.NET中切换到DOUBLE
,你可能注定要失败...