随笔 - 59, 评论 - 580, 引用 - 50

导航

关于


微软提供的免费计数器

山不在高,有仙则名。水不在深,有龙则灵。斯是陋室,唯吾德馨。苔痕上阶绿,草色入帘青。谈笑有鸿儒,往来无白丁。可以调素琴,阅金经。无丝竹之乱耳,无案牍之劳形。南阳诸葛庐,西蜀子云亭。
子曰∶“何陋之有?”


My name is BEN.
This is my master's "doghouse". hehe...

标签

每月存档

最新留言

广告

 
好久没有上来写点什么东西了,也有一段时间没有到博客堂拨客园上面来采风了,今天一上来就看到这个文章:
数据类型的BUG还是???

里面罗列了一些问题,也许我能略解一二。比如说问题二:

dim ss as double

ss = 400*1000

在VB6中,报越界!!

ss = 400*100000没有异常

其实是这样的,VB6里面对于常数,如果没有带数据类型标志符(例如#)或者小数点,就认为是整数。而对于实际上是什么整数,则根据最小化原则,认为400和1000同为16位带符号整形。而数值计算结果的数据类型和操作数中表示范围最大的相一致(其实大部分语言都是这么定义的。ps:对于VB6及以下版本,似乎没有应用常量传播,要到计算里面才会出错),很明显400*1000的计算结果超出16位带符号整形的表示范围,报越界。而400*100000里面后者被认为是32位带符号数值,因此计算结果也是32位带符号数值,所以不会越界。

请注意,大部分语言的计算过程是有一个中间计算结果的,这个结果跟最终承载变量没有关系,而跟该语言的运算法则相关。在本例当中无论ss被定义为double、long还是别的数值类型,都必然会引发越界,这是由VB6里面的语言定义所引起的。而中间计算结果要经过一个转换过程才能够得到最终变量的数据类型,一般的基本数据类型之间都有“隐式”转换,有的是强制转换,这个一般由语言本身所定义。例如在VB6里面,几乎所有数值类型之间都能够自由的进行隐式转换,但是在C#里面,浮点数转换为整形数字的时候就必须要强制转换。

当然,从某种角度来讲,所有数值类型之间能够自由的隐式是VB6语言定义本身的缺陷,因为这样可能会引发很多“看不见”的问题。但是这实际上是VB6语言本身的定义,而不是设计人员无意识的或者不期望的结果,所以我宁愿称之为Fault也不愿意说是Bug。

可是大坏蛋却说.NET里面:
 double ss;
 int firstInt = 2147483646;
 int secondInt = 2;
 ss = firstInt + secondInt;
 Console.WriteLine(ss);

结果:ss = -2147483648

似乎对这个现象有点意见。首先,还是那个原因,计算是有中间结果的,中间结果的类型在这里仍然是int。其次,要追溯C语言本身的处理方式,在C语言里面不会对整形的上下界超界产生任何疑问,甚至不会报错。因为这个被认为是C语言的“特性”之一,C#“号称”继承了C/C++,那自然也会尽可能继承这些传统习惯,因此他就作为语法规范里面的一部分了,无可厚非。而事实上这也不是.NET Framework的功劳,而仅仅是C#的定义而已。因为在VB.NET里面,这会产生异常的。因为在C#的编译器对整数加减法使用的是不带检验的IL指令,而VB.NET则使用的是待检验的IL指令。比如C#使用的是add指令,而VB.NET则使用的是add.ovf指令。当然,这是在最普遍的代码编写方式,以及默认的语言参数下面而言的。

如果有什么疑问,请尝试下述代码:

 int ss;
 int firstInt = 2147483646;
 int secondInt = 2;
 ss = firstInt + secondInt;
 Console.WriteLine(ss);

呵呵,现在再请没有疑问的尝试下述代码:
int ss = int.MaxValue + 2;

回过头来我们再看看第一个问题:

dim ss as double

ss = 194268.02 – 194268

肉眼可以判断结果为0.02,而VB中计算的结果:ss = 0.199999999895226E-02

ss = 1.2 - 1 VB计算的结果为:0.2


要知道这个问题的答案,我们首先要看看这里的浮点数到底是什么浮点数。在.NET Framework里面(以及VB/VC等)遵循的是IEEE标准,那么为什么0.02不是0.02了呢?其实这个在IEEE里面可以找到一个快速的解答。那么为什么后面一个计算会是正确的呢?那其实是因为“精度”足够,使得你认为它就是0.2。事实上IEEE浮点数永远不可能精确等于2的n次幂相加所构成的数值(比如1.375 = 20 + 2-2 + 2-3,我“简称”这种数字为“可被2整除的数字”。),除非IEEE更改了他的标准。(关于IEEE浮点数的定义可以参考这里。)顺带给出double的1.2和0.2的十六进制编码:
1.2 = IEEE_double(3FF3 3333 3333 3333)
0.2 = IEEE_double(3FC9 9999 9999 999A)
而1.2-1的运算结果却是 IEEE_double(3FC9 9999 9999 9998),看到了吗?其实1.2 - 1并不等于0.2的。而事实上IEEE浮点运算即使是在“可被2整除”的数字之间进行,通常都会有误差的,这主要源于精度丢失。前面的1.2 - 1的误差并不属于这个范畴,这主要是由于操作数本身无法被精确表示而造成的(虽然也有精度丢失的原因)。可以说精度不丢失的情况是相当特殊的,比如说完全相等的两个数相加减,乘、除以2的整倍或者正负1以及0,和0或者“非数字”之间的计算,等等。

所以说这些问题千万不要往MS的头上扣,也不是MS所能够改变得了的。

打印 | 张贴于 2004-12-08 17:56:00 | Tag:.NET 技术内幕

留言反馈

#回复: 数据类型的问题 编辑
2007-12-18 05:08:00 | [匿名:wow power leveling]
#re: 数据类型的问题 编辑
ss = 0.199999999895226E-02
(这里应该是打错了吧) 应该是:1.99999999895226E-02
2005-11-11 15:32:00 | [匿名:路过]
#re:数据类型的问题 编辑
数据类型的问题ooeess
2005-06-10 16:31:00 | [匿名:测色仪]
#re:数据类型的问题 编辑
^_^,Pretty Good!
2005-04-16 00:13:00 | [匿名:超声波液位计]
#re:数据类型的问题 编辑
^_^,Pretty Good!
2005-04-10 19:50:00 | [匿名:温湿度表]
#re: 数据类型的问题 编辑
今天温习<.net框架程序设计>,注意到这些,在p131~133有详细说明关于数据溢出,C#默认不检测溢出而交由程序员来预期.


clr提供了add和add.ovf指令,还有sub/sub.ovf,mul/mul.ovf以及conv/conv.ovf运算指令,

int ss;
int firstInt = 2147483646;
int secondInt = 2;
ss = checked(firstInt + secondInt);
Console.WriteLine(ss);


ss = 400*100000没有异常是因为100000隐式转换为Int64,结果集如果超过64位同样溢出
2004-12-25 01:33:00 | [匿名:Truly]
#re: 数据类型的问题 编辑
@ahnan:
Thanks! 笔误……

@大坏蛋:
此言差矣,如果你要那么修正的话,其实反而有可能引起误差。因为现在你看到的那个误差只是一个极小的数字,之所以你觉得有很大的误差,是因为视觉上的缘故。如果194268.02 – 194268 被修正为0.02,实际上你的误差可能就变成了正负0.005了。还是另外开一个帖子说这个问题吧,解释起来太长了。不过这个帖子我想还是放在博客园,不然大家觉得我占用宝贵的版面了。
2004-12-09 15:24:00 | [匿名:sumtec]
#re: 数据类型的问题 编辑
两天前的一个帖子:数据类型的BUG还是???引发大家的讨论,其实道理和如何规避该问题我也知道如何解决.但我觉得,两数相减,其结果的小数位长度不会大于与运算的两个数中小数位数最长的,整数位的长度不会大于参与运算的两个数中整数位最长的那个数的整数位长度+1.如果我来实现double的-操作运算符,我觉得我应该加上这个判断标准来纠正产生的精度损失.
2004-12-09 10:33:00 | [匿名:大坏蛋]
#re: 数据类型的问题 编辑
"而1.2-0.2的运算结果却是 IEEE_double(3FC9 9999 9999 9998),看到了吗?其实1.2 - 0.2并不等于0.2的。"
手误啦? 其实是想说“1.2-1“?
2004-12-09 09:15:00 | [匿名:ahnan]
对不起,目前本随笔不允许发表新评论.

Powered by: Joycode.MVC引擎 0.5.2.0