在SQL中重新创建.NET舍入行为

时间:2016-04-06 15:50:54

标签: c# sql .net sql-server tsql

我正在尝试将一个微妙的计算从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。有谁知道我怎么能做到这一点?

2 个答案:

答案 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,你可能注定要失败...