XuGang

记录一个程序员的成长

 

关于数据类型导致的精确计算

 

实数也称为浮点数

浮点就是小数点的位置不固定,与此相反有定点数,即小数点的位置固定。

整数可以看做是一种特殊的定点数,即小数点在末尾

一般的浮点数有点象科学计数法,包括:符号位指数部分尾数部分。


在C#中,有3中实数型变量类型:float、double、decimal。

 

关于decimal有两点很重要:
一是decimal仍然是浮点数! 浮点数不能用于精确计算,将decimal用于财务运算是不恰当甚至是很错误的说法。只有整型才能用于精确计算。

二是decimal与传统浮点数如float和double的主要区别是这是个十进制浮点数。

简单的说就是二进制浮点数的有效位数是以二进制来计算的(也就是有效位数是二进制位),这样导致的问题是一个看起来没有超出精度的十进制数,用二进制表示的时候会超出有效位数。

为什么会有这种情况?

我们假设有一种三进制数,只有三个数字012,如果我们要表示3则是10,3/3=1用三进制表示也是1,但再除以三如1/3用三进制就是0.1,用十进制呢?就是无限循环小数。也就是说同一个数1/3,用不同的进制,一个只有一位小数,一个有无穷位小数。
 
“decimal 关键字表示 128 位数据类型。同浮点型相比,decimal 类型具有更高的精度和更小的范围,这使它适合于财务和货币计算。”这是MSDN里的原话,一般的理解,虽然decimal也有误差,但精度足够高就行了。
 但是,MSDN中的说法是不够负责任的。而其实MSDN中不恰当的说法并不罕见。

 

个人观点认为,有误差和没有误差才是问题的关键,而不是误差的大小。程序员应当有这种思维。
在牵涉到金额的计算中,应采用用于精确计算的整型并明确使用checked语句,或者在编译选项中选择。如果只是用于呈现和其他简单逻辑,并不牵涉到实际交割金额,decimal对于在大多数情况下都是合适的。
 
其实不是有损和无损的区别,因为整型也必然会损失计算结果,例如小数部分。所以我用的词是精确计算,老实说这个很少有人关注。

简单的说,整型虽然也不可能完整的保留计算结果,但下面的表达式的结果都恒等于i(如果没有溢出的话):

i + j - j
i * j / j
i / j * j + i % j

但浮点数都不满足上述恒等。
这些恒等代表了什么呢?并不是代表我们不会出现计算误差,例如1/3,用十进制是永远不能精确表示的,也就是说计算误差是不可避免的。但整型精确计算的含义在于,在计算过程中不会丢失数据。

简单地说。
10 / 3 = 3
3 * 3 = 9
10 - 9 = 1,我们看到有一个1的误差。

但是!
10 / 3 * 3 + 10 % 3 却恒等于 10(这是上面列出的第三个恒等式)。

如果用浮点数,我们会得到3.33333333,这个更精确,但丢掉的0.000000003333333....这个部分我们是找不回来的。(即使10 - ( 10 / 3 * 3 )这样的表达式也找不回来,因为浮点数连上面的i + j - j这个恒等都不满足)

或许每一次我们丢失的数据都是很小的,但随着时间的推移,这个东西就会累积成巨大的误差导致重大的问题。

还有一个被忽视的问题就是大家都认为浮点数在不超出精度的情况下是不会出现误差的,但实际上有很多时候运算结果不超出精度不代表运算过程不会超出精度。这里是非常难以评估的。

举一个实际的例子,试想想,如果银行账户用的是浮点数,假设它的精度很大,例如有10位十进制,也就是十亿,那么我们存十亿元到银行,然后取一毛钱就会因为精度的问题导致我们的十亿这个存款数额不会变化,这样我们无限制的从银行取钱了,一秒钟取一毛,一年下来也是天文数字。

而使用精确计算的整形呢?因为第一个恒等式的存在,所以就不会存在问题。
 
整型和定点小数本质上是没有区别的,都可以用于精确计算,浮点数无论其精度多大,都不应用于精确计算。利用整型我们可以很方便的做出定点数类型,而不应用浮点型来模拟。这一点我对微软使用decimal来映射数据库的定点小数也是不很满意的,尽管decimal能确保在映射的时候不会丢失任何数据。MSDN 上的这个财务上使用decimal也有很大的误导人的成分。
 
事实上在精确计算的场合,是不会有人用浮点数的,银行应该采用定点小数计算。这个问题就像我所说的,计算的结果不一定会超出精度,但是中间过程可能会。这个很难被发现。

比如说一个很大的数x除以26362777188/3726437552618怎么算呢?

x / ( 26362777188 / 3726437552618 )

好,我们改变下运算顺序:
x * 3726437552618 / 26362777188

第一步的乘法就很可能超出精度了。
 
整型不等于整数。。。。。

财务系统的确是采用整数的,结算单位统一为分就好了。为什么必然产生小数?1.00元和100分是一个概念,只是显示不同。

也就是定点小数与整型没有本质的不同。发现这块关注的人真的挺少的。。。。

浮点数一般用于近似计算,这也可以理解为什么浮点运算强大的CPU在渲染和绘图上比较强,因为这些运算才是浮点数大量使用的场景,也就是近似计算。为什么?比如说距离视点一公里和1.1公里的物体在最终显示上的差距和距离视点1.1米和1.0米的在显示上的差距有多大?这两者虽然一个差100米,一个只差 0.1米,但在渲染成像到2D显示屏上的时候,像素距相近。

 

posted on 2009-10-30 13:35  钢钢  阅读(1254)  评论(1编辑  收藏  举报

导航