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

为什么 Java 直接输出一个(二进制无法表示的)浮点数不会丢失精度?

  •  
  •   theochoi · 2018-07-12 16:24:56 +08:00 · 2700 次点击
    这是一个创建于 2086 天前的主题,其中的信息可能已经有所发展或是发生改变。

    编程新手,关于计算机存储数据的一个问题想请教一下大家。 比如下述代码: —————————————————————————————— double d = 0.3; System.out.printf("%.20f",d); —————————————————————————————— 其中 0.3 应该是不能准确表示成二进制,所以输出的时候从内存中读取 d 的值的时候应该会得到一个很接近 0.3 的数。但是实际最后输出的就是 0.3000...000 。如果把第一句改成 double d = 0.1 + 0.2 的话,结果就会变成 0.3000...0004000...000 了。后面这个结果是可以理解的。 请问前面这个能正确输出 0.3 是为什么呢?

    在 C 下面: —————————————————————————————— double d = 0.3; printf("%.17f",d); —————————————————————————————— 输出的结果是 0.2999...999 。%0.17f 以下的输出都是输出 0.3000...000 ,我对这个的解释是发生了截断。不知道是不是这样?

    11 条回复    2018-07-13 10:12:43 +08:00
    theochoi
        1
    theochoi  
    OP
       2018-07-12 16:25:29 +08:00
    为什么排版变成了这样...TAT
    peng7070
        2
    peng7070  
       2018-07-12 16:48:08 +08:00   ❤️ 1
    java 的浮点数处理最好不要直接加减,使用 bigdecimal 进行加减,不然精度问题很严重的。
    JackEggie
        3
    JackEggie  
       2018-07-12 16:52:09 +08:00   ❤️ 2
    直接输出当然不会丢失精度啊。丢失精度是对计算过程而言的。
    楼主对是否丢失精度和能否准确表示成二进制有误解。

    计算会丢失精度的原因是计算过程中,由于内存中的表示方式位数有限导致的,与能否准确表示成二进制无关哈。
    theochoi
        4
    theochoi  
    OP
       2018-07-12 17:06:50 +08:00 via Android
    @JackEggie 嗯嗯。我想表达的就是为什么 java 能准确表示一个二进制无法表达的小数,0.3 存到内存之后应该已经不是 0.3 了吧?
    JackEggie
        5
    JackEggie  
       2018-07-12 17:16:46 +08:00
    @theochoi 0.3 存下来也是会丢失精度的,只是相对于 0.1+0.2 丢失得少,从二进制转到十进制时是对精度丢失有部分容忍的(也就是所谓的四舍五入)。
    原因要从存储结构上找,原理网上很多,要善用搜索引擎好吧。
    https://www.cnblogs.com/chenfei0801/p/3672177.html
    frienmo
        6
    frienmo  
       2018-07-12 17:19:57 +08:00   ❤️ 1
    @theochoi
    为什么可以显示?因为里面有个对应的表,你一层层点进去看源码。
    public static String toJavaFormatString(float var0) {
    return getBinaryToASCIIConverter(var0).toJavaFormatString();
    }
    theochoi
        7
    theochoi  
    OP
       2018-07-12 17:20:07 +08:00
    @JackEggie 可是问题在于,不管格式化输出多少位,0.3 永远都是准确的。如果有截断发生,肯定在末尾几位会出现非 0 的。
    frienmo
        8
    frienmo  
       2018-07-12 17:24:25 +08:00   ❤️ 1
    @theochoi 0.1 + 0.2
    System.out.println("Max: " + Integer.toString(Float.floatToIntBits(Float.MAX_VALUE), 2));
    System.out.println("1f: " + Integer.toString(Float.floatToIntBits(1f), 2));
    System.out.println("0.1f: " + Integer.toString(Float.floatToIntBits(0.1f), 2));
    System.out.println("0.2f: " + Integer.toString(Float.floatToIntBits(0.2f), 2));
    System.out.println("0.1f + 0.2f: " + Integer.toString(Float.floatToIntBits(0.1f + 0.2f), 2));
    System.out.println("0.3f: " + Integer.toString(Float.floatToIntBits(0.3f), 2));

    System.out.println("1d: " + Long.toString(Double.doubleToLongBits(1d), 2));
    System.out.println("0.1d: " + Long.toString(Double.doubleToLongBits(0.1d), 2));
    System.out.println("0.2d: " + Long.toString(Double.doubleToLongBits(0.2d), 2));
    System.out.println("0.1d + 0.2d: " + Long.toString(Double.doubleToLongBits(0.1d + 0.2d), 2));
    System.out.println("0.3d: " + Long.toString(Double.doubleToLongBits(0.3d), 2));

    个人理解:
    0.1d+0.2d 的时候各自都比较精细,导致了一个更精细的 2 进制值,而这个值又不是和显示 0.3d 对应的值相同。
    lance6716
        9
    lance6716  
       2018-07-12 18:00:45 +08:00 via Android   ❤️ 1
    看一下机器码发生了什么。估计是直接把字面量给输出了
    scmod
        10
    scmod  
       2018-07-13 09:05:37 +08:00
    貌似就是因为误差累积...
    xyjincan
        11
    xyjincan  
       2018-07-13 10:12:43 +08:00
    直接定义,大概有额外的硬件保证精确度
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3222 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 14:23 · PVG 22:23 · LAX 07:23 · JFK 10:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.