double 浮点类型的一些疑问 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jiang1234321
V2EX    C

double 浮点类型的一些疑问

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

    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
    otakusta  
       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
    fengyj
        22
    fengyj  
       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     Solana     1167 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:50 PVG 07:50 LAX 16:50 JFK 19:50
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86