简体   繁体   中英

Multiply long or BigInteger by double without loss of precision

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.

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

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.

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?

You want to use BigDecimal in order to preserve precision.

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

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

    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

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

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);

Double has a precission of 52 bit. How about:

  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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM