首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
华为云
V2EX  ›  C/C++/Obj-C

double 浮点类型的一些疑问

  •  
  •   jiang1234321 · 5 天前 · 1350 次点击

    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
        1
    mm163   5 天前
    System.out.println((double)1-0.9)
        2
    ShuoHui   5 天前 via iPhone   ♥ 1
    蜜汁节点,计算机二进制不能精确表示吧
        3
    zwh2698   5 天前 via Android
    有本书叫计算机组成与原理
        4
    Linyvhan   5 天前
    IEEE754
        5
    johnniang   5 天前 via Android
    说不定被编译器优化了
        6
    FrankFang128   5 天前
    你不能用十进制来思考这个问题
        7
    jiang1234321   5 天前
    @mm163 没啥区别啊,本身就会向高精度类型转换的
        8
    jiang1234321   5 天前
    @Linyvhan 这个知道,就是不知道为什么同样的 0.1,一个可以准确打印,一个不行,还有就是 0.333333333333333*3 怎么就等于 1 了
        9
    jiang1234321   5 天前
    @johnniang 能不能具体一点,或者给个文章什么的看一下,我觉得也是被编译器优化了,
        10
    will0404   5 天前 via iPhone   ♥ 1
    同楼上,不能用十进制来思考计算机的基本运算。你需要补一下计算机基础。

    就答最后一个问题,乘除操作在二进制层面是用移位和简单的加减实现的,不会有精度丢失(除非溢出),而精度是在转为十进制的时候丢失的,1.0/3.0*3.0 是先计算得到了 1.0 的二进制形式,它是可以准确用十进制表示的,当然不会得到 0.99999 …
        11
    ech0x   5 天前 via iPhone
    https://zh.wikipedia.org/zh-hans/浮点数
    如果你真的要涉及到浮点数的输入输出,和不能有错的浮点数运算的话,请用 BigDecimal
        12
    will0404   5 天前 via iPhone
    建议你,CSAPP 第二章读一下。
        13
    liuminghao233   5 天前 via iPhone
    看 csapp 吧
        14
    SuperMild   5 天前
    建议趁此机会练习一下使用搜索引擎,这个问题网上有很多资料,从浅到深都有了,V 站上也是月经。
        15
    icyalala   5 天前   ♥ 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
        16
    CEBBCAT   5 天前 via Android
    @icyalala 有博客吗?没 Google 到
        17
    pipapa   5 天前 via Android
    《深入理解计算机系统》第二章,自己转成二进制手动计算一下就知道了
        18
    VDimos   5 天前 via Android
    浮点的问题就和怎么退出 vim 一样,让无数新手疑惑
        19
    otakustay   5 天前
    摘一段说明得很详细的:



    来源: http://math.ecnu.edu.cn/~jypan/Teaching/Cpp/doc/IEEE_float.pdf
        20
    icyalala   5 天前
    @CEBBCAT https://www.exploringbinary.com/topics/ 见 "Correctly Rounded Decimal to Floating-Point Conversion" 这部分的文章。

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

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

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

    附上 wiki page: https://en.wikipedia.org/wiki/IEEE_754
        27
    icyalala   4 天前
    @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 在内存中的实际数据。
        28
    jiang1234321   4 天前
    @icyalala 感谢,解答了心中的疑惑。
        29
    jiang1234321   4 天前
    @wutiantong 实在是没有这个能力和时间去看一篇英文的文章了,不过还是要感谢你的不求甚解的警钟。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   672 人在线   最高记录 3762   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.1 · 20ms · UTC 19:50 · PVG 03:50 · LAX 12:50 · JFK 15:50
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1