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

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

  •  
  •   theochoi · 151 天前 · 1042 次点击
    这是一个创建于 151 天前的主题,其中的信息可能已经有所发展或是发生改变。

    编程新手,关于计算机存储数据的一个问题想请教一下大家。 比如下述代码: —————————————————————————————— 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
        1
    theochoi   151 天前
    为什么排版变成了这样...TAT
        2
    peng7070   151 天前   ♥ 1
    java 的浮点数处理最好不要直接加减,使用 bigdecimal 进行加减,不然精度问题很严重的。
        3
    JackEggie   151 天前   ♥ 2
    直接输出当然不会丢失精度啊。丢失精度是对计算过程而言的。
    楼主对是否丢失精度和能否准确表示成二进制有误解。

    计算会丢失精度的原因是计算过程中,由于内存中的表示方式位数有限导致的,与能否准确表示成二进制无关哈。
        4
    theochoi   151 天前 via Android
    @JackEggie 嗯嗯。我想表达的就是为什么 java 能准确表示一个二进制无法表达的小数,0.3 存到内存之后应该已经不是 0.3 了吧?
        5
    JackEggie   151 天前
    @theochoi 0.3 存下来也是会丢失精度的,只是相对于 0.1+0.2 丢失得少,从二进制转到十进制时是对精度丢失有部分容忍的(也就是所谓的四舍五入)。
    原因要从存储结构上找,原理网上很多,要善用搜索引擎好吧。
    https://www.cnblogs.com/chenfei0801/p/3672177.html
        6
    frienmo   151 天前   ♥ 1
    @theochoi
    为什么可以显示?因为里面有个对应的表,你一层层点进去看源码。
    public static String toJavaFormatString(float var0) {
    return getBinaryToASCIIConverter(var0).toJavaFormatString();
    }
        7
    theochoi   151 天前
    @JackEggie 可是问题在于,不管格式化输出多少位,0.3 永远都是准确的。如果有截断发生,肯定在末尾几位会出现非 0 的。
        8
    frienmo   151 天前   ♥ 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 对应的值相同。
        9
    lance6716   151 天前 via Android   ♥ 1
    看一下机器码发生了什么。估计是直接把字面量给输出了
        10
    scmod   150 天前
    貌似就是因为误差累积...
        11
    xyjincan   150 天前
    直接定义,大概有额外的硬件保证精确度
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2477 人在线   最高记录 4019   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.1 · 19ms · UTC 14:10 · PVG 22:10 · LAX 06:10 · JFK 09:10
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1