T-SQL ROUND Function Bug?

简介:

先看一个例子:

   1: DECLARE @Value float
   2: SET @Value = 12.1785
   3: SELECT '12.1785' as ValueToRound, ROUND(@Value,3) as RoundedValue
   4: SET @Value = 12.1745
   5: SELECT '12.1745' as ValueToRound,ROUND(@Value,3) as RoundedValue 

猜猜看,结果是多少?这个例子源自:T-SQL ROUND Function Bug

 

1. 问题起因

    一个老系统,每个月要对系统发生的每笔业务数据进行汇总,生成应收款;但是每个月累计下来,总会与每天的连续累加汇总差(相差几分钱,或者几毛钱)。

    同样的问题还有:仓库里面一批按重量计数的物资,每次出库的时候销账;但偶尔会出现:需要出库的数量,与库存中现有数量一致,但依然无法出库的情况。

    问题的根源,就在于数据的存储类型。系统中,选择了使用float来存储数据。但计算机中,是使用近似值来表示float/double的,在这种+/-过程中,误差不断地累积,就出现了上述的这些问题。关于浮点数的存储格式,可以参考我之前的文章:“精确”判断一个浮点数是否等于0

 

2. Linq to SQL中的Round

     知道了问题的原因,我们就可以针对性地进行处理:对数据进行四舍五入后再进行求和。

    C#中,提供了两种控制四舍五入的计算方式:MidpointRounding.ToEven和MidpointRounding.AwayFromZero;默认为MidpointRounding.ToEven,即当一个数字是其他两个数字的中间值时,会将其舍入为最接近的偶数。有关舍入方式,可以参考MSDN:“MidpointRounding 枚举”。

    值得一提的是,里面对MidpointRounding.AwayFromZero的翻译有误,翻译原文为:“AwayFromZero 当一个数字是其他两个数字的中间值时,会将其舍入为两个值中绝对值较小的值。”但英文原文为:“When a number is halfway between two others, it is rounded toward the nearest number that is away from zero.” 文档描述得相当糟糕,不知道是不是我理解能力太差,反正那一坨东西,绕来绕去,还是看里面举的例子比较靠谱。

   1: // 3.4 = Math.Round( 3.45, 1)
   2: //-3.4 = Math.Round(-3.45, 1)
   3:  
   4: // 3.4 = Math.Round( 3.45, 1, MidpointRounding.ToEven)
   5: // 3.5 = Math.Round( 3.45, 1, MidpointRounding.AwayFromZero)
   6:  
   7: //-3.4 = Math.Round(-3.45, 1, MidpointRounding.ToEven)
   8: //-3.5 = Math.Round(-3.45, 1, MidpointRounding.AwayFromZero)

 

    L2S中,如果直接用Math.Round(T.Amount, 2, MidpointRounding.AwayFromZero),则解析成T-SQL就是直接调用Round函数;如果用Math.Round(T.Amount, 2, MidpointRounding.ToEven),则会解析为:

   1: (CASE 
   2: WHEN ((([t0].[Amount]) * 2) = round(([t0].[Amount]) * 2, 2)) AND (([t0].[Amount]) <> round([t0].[Amount], 2)) THEN round(([t0].[Amount]) / 2, 2) * 2
   3: ELSE round([t0].[Amount], 2)
   4: END)
   
3. 解决方法

3.1 两次Round

    使用一次Round可以解决小数点取舍问题,但无法处理浮点数精度带来的误差,下面是一次Round带来的结果:

image

   这是因为,譬如浮点数2053.345在计算机内部存储的格式可能为2053.2449999999……,一次Round的结果就直接把后面49999…..的给舍掉了。但我们可以使用两次Round来达到需要的舍入效果:Round(Round(Value, 6), 2)

image

3.2 Decimal

    处理精度问题,将数据类型定义为Decimal才是王道。

     Decimal在计算及中的存储格式:Decimal 值长度是128位,由 1 位符号、96 位整数以及比例因子组成,比例因子用作 96 位整数的除数并指定整数的哪一部分为小数。比例因子隐式地定为数字 10 的幂,指数范围从 0 到 28。因此,Decimal 值的二进制表示形式为:((-2^96 到 2^96) / 10^(0 到 28))。比例因子还保留 Decimal 数字中的所有尾数零。

本文转自Silent Void博客园博客,原文链接:http://www.cnblogs.com/happyhippy/archive/2011/06/04/2072452.html,如需转载请自行联系原作者

相关文章
|
2月前
|
SQL IDE 关系型数据库
记录一次SQL中的bug的修复过程
记录一次SQL中的bug的修复过程
24 0
|
10月前
|
SQL 前端开发 安全
一个SQL错误的问题让我找到了公司框架中三个bug
本文是对之前开发中遇到的问题的一个总结,文章其实早就写好,但是觉得自己写得不够深入,就让文章一直躺在草稿箱里。昨天突然想起来了,就将文章重新修改了一下,还是发出来吧!
|
SQL 自然语言处理 前端开发
一个 go-sql-driver 的离奇 bug
对于 Go CURD Boy 来说,相信 `github.com/go-sql-driver/mysql` 这个库都不会陌生。基本上 Go 的 CURD 都离不开这个特别重要的库。我们在开发 Seata-go 时也使用了这个库。不过最近在使用 go-sql-driver/mysql 查询 MySQL 的时候,就出现一个很有意思的 bug, 觉得有必要分享出来,以防止后来者再次踩坑。
一个 go-sql-driver 的离奇 bug
|
SQL 关系型数据库 MySQL
一个 go-sql-driver 的离奇 bug
by 京东技术专家 郝洪范对于 Go CURD Boy 来说,相信 github.com/go-sql-driver/mysql 这个库都不会陌生。或许有些人可能没太留意,直接就复制粘贴了 import。比如我们使用 gorm 的时候,如果不加 _ "github.com/go-sql-driver/mysql" 的话,就会报:panic: sql: unknown drive
261 0
一个 go-sql-driver 的离奇 bug