Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit a5c4239

Browse files
authored
Merge pull request #25 from joe-bookwood/multiplication-error-fix
Multiplication error fix and additional tests for the add method of FastMoney
2 parents 2819e6c + 1d85c03 commit a5c4239

File tree

2 files changed

+82
-26
lines changed

2 files changed

+82
-26
lines changed

src/main/java/org/javamoney/moneta/FastMoney.java

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -450,24 +450,38 @@ public FastMoney multiply(Number multiplicand) {
450450
}
451451

452452
private long multiplyExact(long num1, long num2) {
453-
if(num1==0){
454-
return num2;
455-
}
456-
if(num2==0){
457-
return num1;
453+
if(num1==0 || num2==0){
454+
return 0;
458455
}
459-
boolean pos = num1>0 && num2 >0;
460-
boolean neg = num1<0 && num2 <0;
456+
457+
// Hacker's Delight,
458+
int leadingZeros =
459+
Long.numberOfLeadingZeros(num1) +
460+
Long.numberOfLeadingZeros(~num1) +
461+
Long.numberOfLeadingZeros(num2) +
462+
Long.numberOfLeadingZeros(~num2);
463+
464+
if (leadingZeros > Long.SIZE + 1 ) {
465+
// in this case, an overflow is impossible
466+
return num1 * num2;
467+
}
468+
469+
if (leadingZeros < Long.SIZE){
470+
if(Long.signum(num1)*Long.signum(num2) > 0){
471+
throw new ArithmeticException("Long evaluation positive overflow.");
472+
} else {
473+
throw new ArithmeticException("Long evaluation negative overflow.");
474+
}
475+
}
476+
461477
long exact = num1 * num2;
462-
if(pos && exact <=0){
463-
throw new ArithmeticException("Long evaluation positive overflow.");
464-
}
465-
if(neg && exact <=0){
466-
throw new ArithmeticException("Long evaluation negative overflow.");
467-
}
468-
if(!neg && !pos && exact >=0){
469-
throw new ArithmeticException("Long negative overflow.");
478+
479+
// very expensive - this check is only executed in edge cases
480+
// zero check is not needed, see above
481+
if( exact / num1 != num2 ) {
482+
throw new ArithmeticException("overflow");
470483
}
484+
471485
return exact;
472486
}
473487

src/test/java/org/javamoney/moneta/FastMoneyTest.java

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@
1515
*/
1616
package org.javamoney.moneta;
1717

18+
import static org.testng.Assert.assertEquals;
19+
import static org.testng.Assert.assertFalse;
20+
import static org.testng.Assert.assertNotNull;
21+
import static org.testng.Assert.assertNotSame;
22+
import static org.testng.Assert.assertTrue;
23+
import static org.testng.Assert.fail;
24+
25+
import java.io.ByteArrayInputStream;
26+
import java.io.ByteArrayOutputStream;
27+
import java.io.IOException;
28+
import java.io.ObjectInputStream;
29+
import java.io.ObjectOutputStream;
30+
import java.lang.invoke.MethodHandles;
31+
import java.math.BigDecimal;
32+
import java.math.BigInteger;
33+
import java.util.logging.Level;
34+
import java.util.logging.Logger;
35+
1836
import javax.money.CurrencyUnit;
1937
import javax.money.Monetary;
2038
import javax.money.MonetaryAmount;
@@ -24,15 +42,6 @@
2442
import org.junit.Assert;
2543
import org.testng.annotations.Test;
2644

27-
import java.io.*;
28-
import java.lang.invoke.MethodHandles;
29-
import java.math.BigDecimal;
30-
import java.math.BigInteger;
31-
import java.util.logging.Level;
32-
import java.util.logging.Logger;
33-
34-
import static org.testng.Assert.*;
35-
3645
/**
3746
* @author Anatole
3847
*/
@@ -281,7 +290,22 @@ public void testAdd(){
281290
assertNotNull(moneyResult);
282291
assertEquals(11d, moneyResult.getNumber().doubleValue(), 0d);
283292

284-
FastMoney money3 = FastMoney.of(90000000000000L, "CHF");
293+
// This example create a sum that is to big for fastmoney, it should overflow
294+
// 87978089321359 + 4866358678300 = 92844447999659 > 92233720368547.75807
295+
money1 = FastMoney.of(87978089321359L, EURO);
296+
money2 = FastMoney.of(4866358678300L, EURO);
297+
298+
try {
299+
moneyResult = money1.add(money2);
300+
fail("overflow should raise ArithmeticException");
301+
} catch (ArithmeticException e) {
302+
// should happen
303+
}
304+
305+
// check greates FM 92233720368547.75807 value
306+
long fastMoneyMax = 92233720368547L;
307+
FastMoney money3 = FastMoney.of(fastMoneyMax, "CHF");
308+
285309
try {
286310
// the maximum value for FastMoney is 92233720368547.75807 so this should overflow
287311
money3.add(money3);
@@ -409,14 +433,29 @@ public void testMultiplyLong(){
409433
assertEquals(FastMoney.of(200, "CHF"), m.multiply(2));
410434
assertEquals(FastMoney.of(new BigDecimal("50.0"), "CHF"), m.multiply(0.5));
411435

436+
// Zero test
437+
m = FastMoney.of(100, "CHF");
438+
assertEquals( m.multiply(0), FastMoney.of(0, "CHF"));
439+
m = FastMoney.of(0, "CHF");
440+
assertEquals( m.multiply(10), FastMoney.of(0, "CHF"));
441+
412442
try {
413443
// the maximum value for FastMoney is 92233720368547.75807 so this should overflow
414444
FastMoney.of(90000000000000L, "CHF").multiply(90000000000000L);
415445
fail("overflow should raise ArithmeticException");
416446
} catch (ArithmeticException e) {
417447
// should happen
418448
}
419-
}
449+
try {
450+
// the maximum value for FastMoney is 92233720368547.75807
451+
// these values are lower, but the overflow detection does not work
452+
// correct.
453+
FastMoney.of(-53484567177043L, "CHF").multiply(2178802625L);
454+
fail("overflow should raise ArithmeticException");
455+
} catch (ArithmeticException e) {
456+
// should happen
457+
}
458+
}
420459

421460
/**
422461
* Test method for {@link FastMoney#multiply(double)}.
@@ -562,6 +601,9 @@ public void testNegate(){
562601
} catch (ArithmeticException e) {
563602
// should happen
564603
}
604+
605+
m = FastMoney.of(0, "CHF");
606+
assertEquals(m.negate(), FastMoney.of(0, "CHF"));
565607
}
566608

567609
/**

0 commit comments

Comments
 (0)