You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Java中double转long转换差异之谜:为何a与b结果不等?

为什么带随机正负号时ab结果不等,去掉正负号就一致?

首先咱们先把你的代码贴出来方便对照:

public static void main(String[] args) {
    long a = 0;
    long b = 0;
    Random r = ThreadLocalRandom.current();
    for (int i = 0; i < 1000; i++) {
        double d = r.nextDouble()*(r.nextBoolean() ? 1.0 : -1.0);
        a += d*100;
        b += (long)(100*d);
        //System.out.println(a + " " + b + " " + d);
    }
    System.out.println(a);
    System.out.println(b);
}

这个问题的核心其实是Java中double转long的截断规则,以及正负值场景下的精度表现差异,咱们分两种情况说:

1. 去掉正负符号时,结果为什么一致?

当你移除随机正负逻辑,d[0,1)之间的double值,这时候100*d的范围是[0,100),小数部分始终在[0,1)区间内。

  • 对于a += d*100:执行时会先把a(long类型)转成double,和d*100相加,得到的结果是「整数部分 + 小于1的小数部分」。把这个double转成long时,会直接向零截断小数,结果就等于a + (long)(d*100)
  • 对于b += (long)(100*d):直接把100*d的小数部分截断,取整数部分加到b上。

两者每次循环的增量完全一样,累加1000次后结果自然一致。

2. 带随机正负符号时,结果为什么不等?

d可以是负数时,100*d的范围变成(-100,100),小数部分可能落在(-1,0)区间内,这时候就会触发两个关键差异:

(1)截断规则的隐性差异

Java里double转long是向零取整——简单说就是不管正负,直接砍掉小数部分:比如1.9转long是1-1.9转long是-1(不是-2)。

举个极端例子:假设某次100*d的double值是-0.9999999999999999(非常接近-1但比-1大),直接转long会得到0;但如果把这个值加到a的double值上,比如a当前是0,0 + (-0.9999999999999999)会因为double的精度舍入,被处理成-1.0,转long后得到-1——这时候a的增量是-1,而b的增量是0,一次循环就拉开了差距。

(2)精度误差的累积

double的有效位数只有53位,单次累加的误差极小,但正负值交替累加时,误差可能会被累积放大。比如多次累加极小的正负数后,a的double值可能会偏离整数,转long时的截断结果和b的精确long累加结果就会出现偏差。

你可以试试这个简化的测试用例,一眼就能看到差异:

public static void main(String[] args) {
    long a = 0;
    long b = 0;
    // 构造一个d,让100*d刚好是-0.9999999999999999
    double d = -0.9999999999999999 / 100;
    for (int i = 0; i < 1000; i++) {
        a += d*100;
        b += (long)(100*d);
    }
    System.out.println(a); // 输出 -1000
    System.out.println(b); // 输出 0
}

内容的提问来源于stack exchange,提问作者user4179961

火山引擎 最新活动