@wxf
2017-04-30T11:50:42.000000Z
字数 1521
阅读 1602
老马说编程
前面的章节中我们在进行小数运算时,遇到了计算结果不精确的问题。为什么看上去非常简单的运算,计算机怎么会出错了呢?
实际上,不是运算本身出的错,而是因为计算机根本就不能精确的表示很多树,比如0.1这个数。
计算机是用一种二进制格式存储小数的,这个二进制格式不能精确表示0.1,它只能表示一个非常接近0.1但又不等于0.1的一个数。
在计算机中只能用若干个之和来表达十进制的小数,如下:
二进制 | 十进制 | 二进制 | 十进制 |
---|---|---|---|
…… | …… | 0.5 | |
8 | 0.25 | ||
4 | 0.125 | ||
2 | 0.125 | ||
1 | …… | …… |
结合上面的描述,我们来试一试如何表达十进制的0.2,如下:
若干个之和 | 结果 | 描述 |
---|---|---|
0.25 | 太大 | |
0.125 | 又太小 | |
+ | 0.1875 | 逼近0.2了 |
++ | 0.21875 | 又大了 |
++ | 0.203125 | 还是大 |
++ | 0.1953125 | 接近了 |
+++ | 0.19921875 | 很接近 |
由此可见,数字都不能精确表示,在不精确数字上的运算结果不精确也就不足为奇了。
为什么不能使用我们熟悉的十进制呢?在最底层,计算机使用的电子元器件只能表示两个状态,通常是低压和高压,对应0和1,使用二进制容易基于这些电子器件构建硬件设备和进行运算。而且效率高。
在编程过程中,你会发现有的计算结果是精确的。比如,我用Java写:
System.out.print(0.1f+0.1f);
System.out.print(0.1f*0.1f);
第一行输出0.2,第二行输出0.010000001。按照上面的说法,第一行结果也不对啊?
其实,这是Java语言给我们造成的假象,计算结果其实也是不精确的,但是由于结果和0.2足够接近,在输出的时候,Java选择了输出0.2这个看上去非常精确的数字,而不是一个中间有很多0的小数。
计算不精确怎么办?大部分情况下,我们不需要那么高的精度,可以四舍五入,或者在输出的时候只保留固定个数的小数位。
如果真的需要比较高的精度,可以采用如下几种方法:
BigDecimal
,运算更准确,但是效率比较低。我们之前一直在用“小数”这个词表示float和double类型,其实,这是不严谨的,“小数”是在数学中的词,在计算机中,我们一般说的是“浮点数”。float和double被称为浮点数据类型,小数运算被称为浮点运算。
为什么叫浮点数呢?这是由于小数的二进制表示中,表示那个小数点的时候,点不是固定的,而是浮动的。
查看浮点数的二进制形式,在Java中,可以使用如下代码:
Integer.toBinaryString(Float.floatToIntBits(value));
Long.toBinaryString(Double.doubleToLongBits(value));
浮点数运算为什么会出错呢?原因就是:很多小数在计算机中不能精确表示。
计算机的基本思维是二进制的,所以,意料之外,情理之中。
上节我们说了整数的二进制,本节谈了小数。那么字符和文本呢?编码有事怎么回事?乱码是因为什么?
参考资料:
老马说编程(微信号:laoma_shuo)——小数计算为什么会出错?
码农翻身(微信号:coderising)——浮点数为什么不精确?