本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接http://item.jd.com/12299018.html


违反直觉的事实

计算机之所以叫"计算"机就是因为发明它主要是用来计算的,"计算"当然是它的特长,在大家的印象中,计算一定是非常准确的。但实际上,即使在一些非常基本的小数运算中,计算的结果也是不精确的。

比如:

float f = 0.1f*0.1f;
System.out.println(f);

这个结果看上去,不言而喻,应该是0.01,但实际上,屏幕输出却是0.010000001,后面多了个1。

看上去这么简单的运算,计算机怎么会出错了呢?

简要答案

实际上,不是运算本身会出错,而是计算机根本就不能精确的表示很多数,比如0.1这个数。

计算机是用一种二进制格式存储小数的,这个二进制格式不能精确表示0.1,它只能表示一个非常接近0.1但又不等于0.1的一个数。

数字都不能精确表示,在不精确数字上的运算结果不精确也就不足为奇了。

0.1怎么会不能精确表示呢?在十进制的世界里是可以的,但在二进制的世界里不行。在说二进制之前,我们先来看下熟悉的十进制。

实际上,十进制也只能表示那些可以表述为10的多少次方和的数,比如12.345,实际上表示的:1*10+2*1+3*0.1+4*0.01+5*0.001,与整数的表示类似,小数点后面的每个位置也都有一个位权,从左到右,依次为 0.1,0.01,0.001,...即10^(-1), 10^(-2), 10^(-3)。

很多数,十进制也是不能精确表示的,比如1/3, 保留三位小数的话,十进制表示是0.333,但无论后面保留多少位小数,都是不精确的,用0.333进行运算,比如乘以3,期望结果是1,但实际上却是0.999。

二进制是类似的,但二进制只能表示哪些可以表述为2的多少次方和的数,来看下2的次方的一些例子:

2的次方 十进制
2^(-1) 0.5
2^(-2) 0.25
2^(-3) 0.125
2^(-4) 0.0625

可以精确表示为2的某次方之和的数可以精确表示,其他数则不能精确表示。

为什么一定要用二进制呢?

为什么就不能用我们熟悉的十进制呢?在最最底层,计算机使用的电子元器件只能表示两个状态,通常是低压和高压,对应0和1,使用二进制容易基于这些电子器件构建硬件设备和进行运算。如果非要使用十进制,则这些硬件就会复杂很多,并且效率低下。

有什么有的小数计算是准确的

如果你编写程序进行试验,你会发现有的计算结果是准确的。比如,我用Java写:

System.out.println(0.1f+0.1f);
System.out.println(0.1f*0.1f);

第一行输出0.2,第二行输出0.010000001。按照上面的说法,第一行的结果应该也不对啊?

其实,这只是Java语言给我们造成的假象,计算结果其实也是不精确的,但是由于结果和0.2足够接近,在输出的时候,Java选择了输出0.2这个看上去非常精简的数字,而不是一个中间有很多0的小数。

在误差足够小的时候,结果看上去是精确的,但不精确其实才是常态。

怎么处理计算不精确

计算不精确,怎么办呢?大部分情况下,我们不需要那么高的精度,可以四舍五入,或者在输出的时候只保留固定个数的小数位。

如果真的需要比较高的精度,一种方法是将小数转化为整数进行运算,运算结束后再转化为小数,另外的方法一般是使用十进制的数据类型,这个没有统一的规范,在Java中是BigDecimal,运算更准确,但效率比较低,本节就不详细说了。

二进制表示

我们之前一直在用"小数"这个词表示float和double类型,其实,这是不严谨的,"小数"是在数学中用的词,在计算机中,我们一般说的是"浮点数"。float和double被称为浮点数据类型,小数运算被称为浮点运算。

为什么要叫浮点数呢?这是由于小数的二进制表示中,表示那个小数点的时候,点不是固定的,而是浮动的。

我们还是用10进制类比,10进制有科学表示法,比如123.45这个数,直接这么写,就是固定表示法,如果用科学表示法,在小数点前只保留一位数字,可以写为1.2345E2即1.2345*(10^2),即在科学表示法中,小数点向左浮动了两位。

二进制中为表示小数,也采用类似的科学表示法,形如 m*(2^e)。m称为尾数,e称为指数。指数可以为正,也可以为负,负的指数表示哪些接近0的比较小的数。在二进制中,单独表示尾数部分和指数部分,另外还有一个符号位表示正负。

几乎所有的硬件和编程语言表示小数的二进制格式都是一样的,这种格式是一个标准,叫做IEEE 754标准,它定义了两种格式,一种是32位的,对应于Java的float,另一种是64位的,对应于Java的double。

32位格式中,1位表示符号,23位表示尾数,8位表示指数。64位格式中,1位表示符号,52位表示尾数,11位表示指数。

在两种格式中,除了表示正常的数,标准还规定了一些特殊的二进制形式表示一些特殊的值,比如负无穷,正无穷,0,NaN (非数值,比如0乘以无穷大)。

IEEE 754标准有一些复杂的细节,初次看上去难以理解,对于日常应用也不常用,本文就不介绍了。

如果你想查看浮点数的具体二进制形式,在Java中,可以使用如下代码:

Integer.toBinaryString(Float.floatToIntBits(value))
Long.toBinaryString(Double.doubleToLongBits(value));

小结

小数计算为什么会出错呢?理由就是:很多小数计算机中不能精确表示。

计算机的基本思维是二进制的,所以,意料之外,情理之中!

上节我们说了整数的二进制,本节谈了小数。

那字符和文本呢?编码是怎么回事?乱码又是什么原因?

----------------

未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),深入浅出,老马和你一起探索Java编程及计算机技术的本质。原创文章,保留所有版权。

-----------

更多相关原创文章

计算机程序的思维逻辑 (1) - 数据和变量

计算机程序的思维逻辑 (2) - 赋值

计算机程序的思维逻辑 (3) - 基本运算

计算机程序的思维逻辑 (4) - 整数的二进制表示与位运算

计算机程序的思维逻辑 (6) - 如何从乱码中恢复 (上)?

计算机程序的思维逻辑 (7) - 如何从乱码中恢复 (下)?

计算机程序的思维逻辑 (8) - char的真正含义

Java编程的逻辑 (5) - 小数计算为什么会出错?的更多相关文章

  1. 《Java编程的逻辑》 - 文章列表

    <计算机程序的思维逻辑>系列文章已整理成书<Java编程的逻辑>,由机械工业出版社出版,2018年1月上市,各大网店有售,敬请关注! 京东自营链接:https://item.j ...

  2. Java编程的逻辑 (1) - 数据和变量

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  3. Java编程的逻辑 (2) - 赋值

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  4. Java编程的逻辑 (3) - 基本运算

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  5. Java编程的逻辑 (4) - 整数的二进制表示与位运算

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  6. Java编程的逻辑 (6) - 如何从乱码中恢复 (上)?

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  7. Java编程的逻辑 (7) - 如何从乱码中恢复 (下)?

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  8. Java编程的逻辑 (24) - 异常 (上)

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  9. Java编程的逻辑 (25) - 异常 (下)

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

随机推荐

  1. mysql存储过程学习第一天

    摘要:存储过程和函数是在数据库中定义一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程和函数可以避免开发人员重复的编写相同的SQL语句.而且,存储过程和函数是在 ...

  2. EntityFramework异常The specified cast from a materialized 'System.Double' type to the 'System.Single' type is not valid.

    实体类: public class ReportEntity { public string FactorName { get; set; } public double MaxVal { get; ...

  3. 获取移动端 touchend 事件中真正触摸点下方的元素

    移动端的touchstart, touchmove, touchend三个事件,拖动元素结束时,获取到了touchend事件, 但是event.touches[0].target所指向的元素却是tou ...

  4. Codeforces - 1020B Badge(找环)

    题意: 以每个点为起点,找到第一个出现两次的点 解析: 我是先找出来所有的环  环上的点找出来的肯定是自己 bz[i]  = i; 然后去遍历不在环上的点j  如果通过这个点找到一个已经标记的的点i ...

  5. 【BZOJ4800】[Ceoi2015]Ice Hockey World Championship (meet in the middle)

    [BZOJ4800][Ceoi2015]Ice Hockey World Championship (meet in the middle) 题面 BZOJ 洛谷 题解 裸题吧,顺手写一下... #i ...

  6. Dom选择器--内容文本操作

    一.文本内容操作 内容: <body> <div id="i1"> 学习是我快乐? <a>晚饭吃什么</a> </div> ...

  7. Ajax跨域CORS

    在Ajax2.0中多了CORS允许我们跨域,但是其中有着几种的限制:Origin.Methods.Headers.Credentials 1.Origin 当浏览器用Ajax跨域请求的时候,会带上一个 ...

  8. java基础-多线程应用案例展示

    java基础-多线程应用案例展示 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候 ...

  9. Spring cloud 微服务架构 Eureka篇

    1 服务发现 ## 关于服务发现 在微服务架构中,服务发现(Service Discovery)是关键原则之一.手动配置每个客户端或某种形式的约定是很难做的,并且很脆弱.Spring Cloud提供了 ...

  10. AttributeError: 'module' object has no attribute 'X509_up_ref'

    主要报错: AttributeError: 'module' object has no attribute 'X509_up_ref' 1 解决办法 卸载再重装pyOpenSSL pip unins ...