Java中double转long转换差异之谜:为何a与b结果不等?
为什么带随机正负号时
a和b结果不等,去掉正负号就一致? 首先咱们先把你的代码贴出来方便对照:
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




