= 0.2 + 0.2
=0.4
而如果我们使用的是两位精度,那么计算过程又会变成下面的情况。
0.5 * 0.5 + 0.5*0.5 = 0.25 + 0.25
=0.5
对比两种精度情况下的计算结果,一位精度情况下的计算结果和正确的结果差了0.1。而使用了两位精度的情况则正常的计算出了结果。因此可以发现在计算的过程中保证精度是一件多么有意义的事情。
数字格式的准确度数字格式的表示范围、精度都已经介绍完了,那么接下来我们就来介绍一下数字格式的准确度。刚刚已经说过了,准确度和精度是一对经常让人混淆的概念。
那么我们再通俗的给准确度来个注释,简单的说它表示的是该数字格式(特定环境)所表示的数字和真实数字的误差。准确度越高,则意味着数字格式所表示的数字和真实数字的值之间的误差越小。准确度越低,则意味着数字格式所表示的数字和真实数字的值之间的误差越大。
需要注意的一点是数字格式的精度和数字格式的准确度并没有直接的关系,这一点也是很多朋友在概念上常常会混淆的地方。使用低精度的数字格式表示的数字,并不一定要比使用高精度的数字格式所表示的数字的准确度低。
举一个简单的例子:
Byte num = 0x05; Int16 num1 = 0x0005; Int32 num2 = 0x00000005; Single num3 = 5.000000f; Double num4 = 5.000000000000000;
此时,我们分别使用5种不同的数字格式表示同一个数字5,虽然数字格式的精度(从8位到64位)不同,但是通过数字格式所表示出来的数和真实的数是一样的。也就是说对于数字5,这5种数字格式的准确度相同。
0x04 取整误差了解了计算机中常见的几种数字格式之后,现在我们再来聊一聊计算机是如何通过数字格式来表示现实世界中的数字的。众所周知,计算机中使用的是0和1,即二进制,使用二进制表示整数是十分容易的一件事情,不过在使用二进制表示小数时,我们往往会产生一些疑问。例如二进制小数1110.1101换算成十进制是多少呢?第一眼看上去多了一个小数点,似乎让人十分困惑。事实上它的处理和整数是一样的,即将各个数位的数值和位权相乘结果求和。小数点前的位权,大家都已经十分熟悉了,从右向左分别是0次幂、1次幂、2次幂以此递增,因此小数点前的二进制换算为十进制便是:
1 * 8 + 1 * 4 + 1 * 2 + 0 = 14
而在小数点之后的位权,相应的从左向右分别是-1次幂、-2次幂依次递减。因此小数点之后的二进制转换为十进制便是:
1 * 0.5 + 1 * 0.25 + 0 * 0.125 + 1 * 0.0625 = 0.8125
因此1110.1101这个二进制小数转换为十进制便是14.8125。
通过观察小数点之后的二进制转换为十进制的过程,各位看官是否发现了很有趣的一个事实呢?那就是小数点之后的二进制并不能表示所有的十进制数,换言之有一些十进制数是无法转换成二进制的。这个很好理解,因为小数点之后,二进制的位权按照除以2的节奏递减,而十进制却是按照除以10的节奏递减。因此如果小数点后4位用二进制表示,即从.0000~.1111这个范围内连续的二进制数值事实上对应的十进制数是不连续的,所有可能的结果也不过是各个位权(0.5、0.25、0.125以及0.0625)相加的组合而已。
因此,一个在十进制中十分简单的数字如果用二进制来准确无误的表示,所使用的位数可能会十分长甚至是无限的。一个很好的例子便是使用二进制浮点数来表示十进制中的0.1:
double x = 0.1d;
事实上变量x中所保存的值并不是真正的十进制中的0.1,而是一个最接近十进制0.1的二进制浮点数。这是因为无论小数点之后有多少位二进制的数,2的负数次幂都无法相加得到0.1这个结果,因此0.1这个十进制数在二进制中会变成一个无限小数。
当然二进制有可能无法准确的表示一个十进制小数很好理解,因为这有点类似于在十进制中我们同样无法准确表示1/3这样的循环小数。
此时,我们便不得不和计算机妥协了。因为我们现在知道了在计算机中使用的数值可能并不等于真实世界中的数值,而是计算机使用某种数字格式表示的一个十分接近原始数字的一个值。而在整个程序运行的过程中,我们的计算机就要一直使用这个仅仅是近似的数值来参与计算,我们假设真实的数值是n,而计算机事实上会使用另一个数值n + e(当然e是一个可正可负且十分小的数)来参与计算机中的运算。此时,这个数值e便是取整误差。
而这还仅仅是一个数字在计算机中使用近似值来表示,如果该数值参与到计算中去,那么显然会带来更多误差。这也是本文一开始那个c程序之所以计算错误的原因,因为无法正确的表示参与计算的值,到最后都变成了近似值。当然C#语言相对而言要“高级”了很多,虽然在计算机中也是近似值,但是展示在大家眼前的至少还是更加符合人们“预期”的值。不过在C#中,小数计算真的是不会出错的吗?毕竟,这一切似乎仅仅是障眼法。
0x05 取与舍,C#的小数 比比是否相等不知道各位看官在使用一些关系运算符时,有没有留意到直接使用等号比较两个小数是否相等时是否会出现一些意想不到的问题。我身边的朋友使用关系运算符直接比较两个小数大小的情况比较多,而直接比较两个小数是否相等的情况却不太多。同时我在此也想提醒各位最好不要轻易比较两个小数是否相等,即便在C#这种高级语言中仍然可能得到让人感觉“错误”的答案,这是因为我们事实上比较的是两个小数是否“接近”于相等,而不是两个数是否是真正的相等。下面这个例子可能会更好的说明这一点: