V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jiang1234321
V2EX  ›  C

double 浮点类型的一些疑问

  •  
  •   jiang1234321 · 2018-10-14 10:14:16 +08:00 · 3783 次点击
    这是一个创建于 2234 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1、 System.out.println(0.1); 输出是 0.1

    2、 System.out.println(1-0.9); 输出是 0.09999999999999998

    为什么同样是 0.1,直接打印就 ok,经过计算后就会由于 double 的精度导致输出循环。

    还有,

        double x = 1.0;
        double y = 3.0;
        double z = 3.0;
    
        System.out.println(x/y*z);
    

    为什么输出的是 1 ?应该是 0.9999999999999999999 的循环才对吧。

    29 条回复    2018-10-15 14:16:33 +08:00
    mm163
        1
    mm163  
       2018-10-14 10:19:07 +08:00
    System.out.println((double)1-0.9)
    ShuoHui
        2
    ShuoHui  
       2018-10-14 10:19:18 +08:00 via iPhone   ❤️ 1
    蜜汁节点,计算机二进制不能精确表示吧
    zwh2698
        3
    zwh2698  
       2018-10-14 10:31:54 +08:00 via Android
    有本书叫计算机组成与原理
    Linyvhan
        4
    Linyvhan  
       2018-10-14 10:33:02 +08:00
    IEEE754
    johnniang
        5
    johnniang  
       2018-10-14 10:34:22 +08:00 via Android
    说不定被编译器优化了
    FrankFang128
        6
    FrankFang128  
       2018-10-14 10:35:50 +08:00
    你不能用十进制来思考这个问题
    jiang1234321
        7
    jiang1234321  
    OP
       2018-10-14 10:43:24 +08:00
    @mm163 没啥区别啊,本身就会向高精度类型转换的
    jiang1234321
        8
    jiang1234321  
    OP
       2018-10-14 10:44:19 +08:00
    @Linyvhan 这个知道,就是不知道为什么同样的 0.1,一个可以准确打印,一个不行,还有就是 0.333333333333333*3 怎么就等于 1 了
    jiang1234321
        9
    jiang1234321  
    OP
       2018-10-14 10:44:48 +08:00
    @johnniang 能不能具体一点,或者给个文章什么的看一下,我觉得也是被编译器优化了,
    will0404
        10
    will0404  
       2018-10-14 10:47:58 +08:00 via iPhone   ❤️ 1
    同楼上,不能用十进制来思考计算机的基本运算。你需要补一下计算机基础。

    就答最后一个问题,乘除操作在二进制层面是用移位和简单的加减实现的,不会有精度丢失(除非溢出),而精度是在转为十进制的时候丢失的,1.0/3.0*3.0 是先计算得到了 1.0 的二进制形式,它是可以准确用十进制表示的,当然不会得到 0.99999 …
    ech0x
        11
    ech0x  
       2018-10-14 10:48:22 +08:00 via iPhone
    https://zh.wikipedia.org/zh-hans/浮点数
    如果你真的要涉及到浮点数的输入输出,和不能有错的浮点数运算的话,请用 BigDecimal
    will0404
        12
    will0404  
       2018-10-14 10:49:14 +08:00 via iPhone
    建议你,CSAPP 第二章读一下。
    liuminghao233
        13
    liuminghao233  
       2018-10-14 10:51:04 +08:00 via iPhone
    看 csapp 吧
    SuperMild
        14
    SuperMild  
       2018-10-14 10:53:33 +08:00
    建议趁此机会练习一下使用搜索引擎,这个问题网上有很多资料,从浅到深都有了,V 站上也是月经。
    icyalala
        15
    icyalala  
       2018-10-14 10:55:03 +08:00   ❤️ 1
    可以仔细看一下 IEEE754 浮点数标准与计算过程。精度损失主要集中在这几个地方:
    1. 大部分十进制表示的小数,是不能完全精确的由二进制的 IEEE 浮点数表示出来,编辑器解析字符串的过程中会有 rouding 过程。
    2. 打印的时候,二进制大部分情况也不会完全用十进制表示出来,printf 会做舍入和截断。
    3. 浮点数计算的过程中,可能会有精度损失。
    可以在 https://www.exploringbinary.com/floating-point-converter/ 转换来看看。

    "0.1" 解析后是 0x1.999999999999ap-4
    "0.9" 解析后是 0x1.ccccccccccccdp-1 (注意最后一位)
    1-0.9 得到的结果是 0x1.9999999999998p-4 (注意最后一位)

    1.0 / 3.0 * 3.0 这三个数都能精确表示为 IEEE 浮点数
    1.0 / 3.0 得到结果 0x1.5555555555555p-2
    再乘以 3.0 得到结果 0x1p0
    CEBBCAT
        16
    CEBBCAT  
       2018-10-14 12:02:09 +08:00 via Android
    @icyalala 有博客吗?没 Google 到
    pipapa
        17
    pipapa  
       2018-10-14 12:12:19 +08:00 via Android
    《深入理解计算机系统》第二章,自己转成二进制手动计算一下就知道了
    VDimos
        18
    VDimos  
       2018-10-14 13:44:05 +08:00 via Android
    浮点的问题就和怎么退出 vim 一样,让无数新手疑惑
    otakustay
        19
    otakustay  
       2018-10-14 14:01:21 +08:00
    摘一段说明得很详细的:



    来源: http://math.ecnu.edu.cn/~jypan/Teaching/Cpp/doc/IEEE_float.pdf
    icyalala
        20
    icyalala  
       2018-10-14 14:25:12 +08:00
    @CEBBCAT https://www.exploringbinary.com/topics/ 见 "Correctly Rounded Decimal to Floating-Point Conversion" 这部分的文章。

    CSAPP 可以作为基础知识来了解一下,精度究竟是如何损失的、损失结果是怎么样的,这部分就没有介绍了。

    把字符串解析为 IEEE754 浮点数,是非常复杂的事情,vc、gcc、jdk 甚至 libc 的 strtod 都出现过 bug。
    IEEE754 浮点数运算,又是另一个话题了。
    CEBBCAT
        21
    CEBBCAT  
       2018-10-14 15:35:03 +08:00
    @icyalala #20 我想要的是您的博客,想加个 RSS
    huiyifyj
        22
    huiyifyj  
       2018-10-14 15:45:48 +08:00 via Android
    计算机组成与原理,IEEE754 规格化和浮点数运算这方面请认真读。
    pythonee
        23
    pythonee  
       2018-10-14 19:06:23 +08:00
    好久没有在 V2EX 看到这类的技术问题了
    limbo0
        24
    limbo0  
       2018-10-15 01:16:05 +08:00 via Android
    经典问题哈,就是 2 进制保存小数不像整数一样,记得是 x/2**n 这种除以 2 次幂的形式,会有精度问题,运算时会损失精度,就是你看到的样子
    jiang1234321
        25
    jiang1234321  
    OP
       2018-10-15 10:18:20 +08:00
    @icyalala 万分感谢,还有问题就是,解析后是这些数字在内存当中实际存储的值吗?
    那为什么一个数字直接打印没问题,计算得到的打印就会循环呢?就像是问题描述的那样。
    wutiantong
        26
    wutiantong  
       2018-10-15 10:57:12 +08:00
    @Linyvhan #4 已经给出了最本质的解答:IEEE754
    @jiang1234321 楼主看了一眼后回复了一句“这个我知道”就选择无视继续反复他的“疑问”。

    事实上,假如楼主真的知道 IEEE754 在讲什么,他就不应该还有这些疑问。
    所以我建议楼主早日克服自己不求甚解的毛病。

    附上 wiki page: https://en.wikipedia.org/wiki/IEEE_754
    icyalala
        27
    icyalala  
       2018-10-15 11:40:33 +08:00
    @jiang1234321

    0.1 和 0.9 用二进制表示的话,都是无限循环的,但内存中的 double 只有有限位数,所以要做截断并 rounding 到最近的一个二进制,这里损失了精度。

    "0.1" 解析后是 0x1.999999999999ap-4
    "0.9" 解析后是 0x1.ccccccccccccdp-1
    "1" 解析后是 0x1p0
    1-0.9 实际在内存中的计算是 0x1p0 - 0x1.ccccccccccccdp-1 = 0x1.9999999999998p-4

    直接写的 0.1 和经过计算得到的 0.1 在内存中的数值不相同,相差 2 ulp。

    0x1.999999999999ap-4 转换为十进制时,是 0.1000000000000000055511151231257827021181583404541015625,print 会截断到 0.10000000000000000,舍去结尾的 0,就是 0.1。

    0x1.9999999999998p-4 转换为十进制时,是 0.09999999999999997779553950749686919152736663818359375,print 会截断到 0.09999999999999998。

    上面的这些 0x 开头的数值是用 Hexadecimal 格式写的,等同于 double 在内存中的实际数据。
    jiang1234321
        28
    jiang1234321  
    OP
       2018-10-15 14:14:56 +08:00
    @icyalala 感谢,解答了心中的疑惑。
    jiang1234321
        29
    jiang1234321  
    OP
       2018-10-15 14:16:33 +08:00
    @wutiantong 实在是没有这个能力和时间去看一篇英文的文章了,不过还是要感谢你的不求甚解的警钟。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5936 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 01:48 · PVG 09:48 · LAX 17:48 · JFK 20:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.