java 将 long 或 BigInteger 乘以 double 而不损失精度

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16543470/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 23:16:03  来源:igfitidea点击:

Multiply long or BigInteger by double without loss of precision

javadoubleprecisionlong-integerbiginteger

提问by catalyst

I want to multiply a high precision integer (long or BigInteger) by a small double (think about something > 0 and < 1) and get the arithmetically rounded integer (long or BigInteger) of the exact arithmetic value of the operation as result.

我想将一个高精度整数(long 或 BigInteger)乘以一个小双精度数(想想 > 0 和 < 1 的东西),然后得到运算的精确算术值的算术四舍五入整数(long 或 BigInteger)作为结果。

Converting the double to integer does not work, because its fractional value gets lost.

将 double 转换为 integer 不起作用,因为它的小数值会丢失。

Converting the integer to double, then multiply and converting the result back to integer will not work either, because double is not precise enough. Of course you could argue, that because the double operand is not precise enough in the first place, it might not matter that the result is not precise with the same order of magnitude, but in this case, it does.

将整数转换为 double,然后乘法并将结果转换回整数也不起作用,因为 double 不够精确。当然,您可能会争辩说,因为首先双操作数不够精确,所以结果在同一数量级上不精确可能无关紧要,但在这种情况下,确实如此。

Bonus question:

奖金问题:

Using BigDecimal works, but seems to be very inefficient. Converting long to double and then multiplying and converting back seems to run 500 times faster (albeit losing precision) than converting to BigDecimal. Is there a more efficient possibility? Is it possible to gain performance when multiplying several different long each with the same double?

使用 BigDecimal 有效,但似乎效率很低。将 long 转换为 double 然后乘以再转换回来似乎比转换为 BigDecimal 快 500 倍(尽管失去了精度)。有没有更有效的可能性?将多个不同的 long 与相同的 double 相乘是否可以获得性能?

回答by devnull

You want to use BigDecimalin order to preserve precision.

您想使用BigDecimal以保持精度。

BigInteger myBI = new BigInteger("99999999999999999");
Double d = 0.123;
BigDecimal bd = new BigDecimal(myBI);
BigDecimal result = bd.multiply(BigDecimal.valueOf(d));

回答by catalyst

Using BigDecimal indeed works. You still have to be carefull about using the exact value the double represents and rounding arithmetically.

使用 BigDecimal 确实有效。您仍然必须小心使用 double 表示的确切值并按算术四舍五入。

    BigInteger myBI = new BigInteger("1000000000000000000000000000000000000000000000000000000");
    double d = 0.1;
    BigDecimal bd = new BigDecimal(myBI);

    BigInteger doubleWithStringValue = bd.multiply(BigDecimal.valueOf(d)).toBigInteger();

    BigDecimal bdresult = bd.multiply(new BigDecimal(d));
    BigInteger unrounded = bdresult.toBigInteger();
    BigInteger correct = bdresult.add(new BigDecimal("0.5")).toBigInteger(); // this way of rounding assumes positive numbers
    BigInteger lostprecision = new BigDecimal(myBI.doubleValue() * d).toBigInteger();

    System.out.println("DoubleString:   " + doubleWithStringValue);

    System.out.println("Unrounded:      " + unrounded);
    System.out.println("Correct:        " + correct);
    System.out.println("Lost precision: " + lostprecision);

Output:

输出:

DoubleString:   100000000000000000000000000000000000000000000000000000
Unrounded:      100000000000000005551115123125782702118158340454101562
Correct:        100000000000000005551115123125782702118158340454101563
Lost precision: 100000000000000020589742799994816764107083808679919616

回答by wolfcall

The best solution I can see is you use the Math.round function. with code like this.

我能看到的最佳解决方案是您使用 Math.round 函数。用这样的代码。

long l; //your long value
double d;//your fraction
long answer;

answer = Math.round((double)(l * d));

This will give you the answer without a lost prevention error. The other option would be to truncate it.

这将为您提供没有丢失预防错误的答案。另一种选择是截断它。

same declares as above code.

与上述代码相同的声明。

String s;

s = "" + (l*d);
StringTokenizer token = new StringTokenizer(s);
s = token.nextToken();
answer = Long(s);

回答by Reto Z

Double has a precission of 52 bit. How about:

Double 的精度为 52 位。怎么样:

  1. multiplying your double by (1<<52)
  2. convert double to BigInteger (no loss as full precision is on left of decimal point)
  3. multiply with other BigIngeger
  4. partially correct binary exponent of result (BigInteger>>51)
  5. If odd, do your rounding by adding 1 or BigInteger.Sign (depending on your preferences of rounding)
  6. Finally shift result one more bit (BigInteger>>1)

    BigInteger myBI = BigInteger("99999999999999999");
    Double d = 0.123;
    BigInteger bigDouble = (BigInteger)(d * ((ulong)1 << 52));
    BigInteger result = (myBI * bigDouble) >> 51; if (!result.IsEven)
    result += result.Sign; result = result >> 1;

  1. 将你的双倍乘以 (1<<52)
  2. 将 double 转换为 BigInteger(没有损失,因为小数点左边是全精度)
  3. 与其他 BigIngeger 相乘
  4. 结果的部分正确二进制指数 (BigInteger>>51)
  5. 如果奇怪,请通过添加 1 或 BigInteger.Sign 来进行舍入(取决于您对舍入的偏好)
  6. 最后将结果再移一位 (BigInteger>>1)

    BigInteger myBI = BigInteger("99999999999999999");
    双 d = 0.123;
    BigInteger bigDouble = (BigInteger)(d * ((ulong)1 << 52));
    BigInteger 结果 = (myBI * bigDouble) >> 51; if (!result.IsEven)
    result += result.Sign; 结果 = 结果 >> 1;