Rational calculation: add
, subtract
, multiply
, divide
and pow(int)
with exact precision in Java.
The handling of BigDecimal
is a bit cumbersome. The programmer needs to specify the precision (or MathContext
) for almost every division, and - depending on the call chain - for the other arithmetic operations as well.
My goal is to provide an easy to use class which frees the user of taking care about precision.
The only point where the programmer has to make some assumptions about precision should be at the end of all the calculations, when she needs to convert the final result back into a custom value.
Every decimal value in the form 12345.6789
can be represented as a rational number, also called fraction. In this example, it would be 123456789 / 10000
.
Arithmetics with fractions can be reduced to integer multiplications, integer additions and integer subtractions. And those can be performed with exact precision.
<dependency>
<groupId>st.extreme</groupId>
<artifactId>fractions</artifactId>
<version>1.1</version>
</dependency>
Please adapt to your build system of choice. For more information, see Releases.
Immutable arbitrary precision fractions.
The implementation is based on a BigInteger
value for both numerator and denominator.
All operations on fractions can be performed through BigInteger
multiplication, addition and subtraction. And these have exact precision.
Cancellation is done on construction, using BigInteger.gcd(BigInteger)
.
This is the most common source of rounding problems.
@Test
public void testUseCase1() {
BigFraction start = BigFraction.valueOf("1000");
BigFraction divisor = BigFraction.valueOf("21");
BigFraction quotient = start.divide(divisor);
BigFraction result = quotient.multiply(divisor);
assertEquals("1000", result.toString());
assertEquals("1000", result.toPlainString());
assertEquals(start, result);
}
@Test
public void testUseCase1_BigDecimal() {
BigDecimal start = new BigDecimal("1000");
BigDecimal divisor = new BigDecimal("21");
BigDecimal quotient = start.divide(divisor, new MathContext(30, RoundingMode.HALF_UP));
BigDecimal result = quotient.multiply(divisor);
assertEquals("1000", result.toPlainString());
assertEquals(start, result);
}
While testUseCase1()
passes, testUseCase1_BigDecimal()
fails. It is very hard to find a MathContext
so that the second test passes.
@Test
public void testUseCase2_multiply() {
BigFraction bf1 = BigFraction.valueOf("2/3");
BigFraction bf2 = BigFraction.valueOf("-6/7");
BigFraction result = bf1.multiply(bf2);
assertEquals("-4/7", result.toString());
}
@Test
public void testUseCase2_divide() {
BigFraction bf1 = BigFraction.valueOf("2/3");
BigFraction bf2 = BigFraction.valueOf("6/7");
BigFraction result = bf1.divide(bf2);
assertEquals("7/9", result.toString());
}
@Test
public void testUseCase2_add() {
BigFraction bf1 = BigFraction.valueOf("2/15");
BigFraction bf2 = BigFraction.valueOf("6/5");
BigFraction result = bf1.add(bf2);
assertEquals("4/3", result.toString());
}
@Test
public void testUseCase2_subtract() {
BigFraction bf1 = BigFraction.valueOf("8/15");
BigFraction bf2 = BigFraction.valueOf("6/5");
BigFraction result = bf1.subtract(bf2);
assertEquals("-2/3", result.toString());
}
@Test
public void testUseCase2_pow() {
BigFraction bf1 = BigFraction.valueOf("-2/3");
BigFraction result = bf1.pow(-3);
assertEquals("-27/8", result.toString());
}
- The implementation of
toString()
andtoPlainString()
have switched. This reflects the benaviour ofBigDecimal.toString()
andBigDecimal.toPlainString()
. - The
.valueOf(String)
parsing of input can now handle more number-alike strings, especiallyBigDecimal.toEngineeringString()
andBigDecimal.toString()
with scientific notation. - Numerator and denominator now keep their signs. This will be reverted with the next release.
- Running this library now requires Java 8.
- The
MANIFEST.MF
file now has an attributeAutomatic-Module-Name
with the valuest.extreme.math.fraction
, in order to ease the transition to Java 9. - Due to the automatic module name, the package for
BigFraction
is nowst.extreme.math.fraction
, instead ofst.extreme.math
before. equals()
is now properly implemented and verified by EqualsVerifier. As a consequence, theBigFraction
class is nowfinal
.- The
compareTo()
method now only acceptsBigFraction
any more (everything else would violate theComparable
contract). Thanks to cancellation, the natural ordering ofBigFraction
is now consistent withequals()
. - The new
compareToNumber()
method allows comparisons tojava.lang.Number
types. - The
denominator
now is always kept positive (again). Some optimizations required this, so it is likely to stay this way.
The following people gave very valuable advice - many thanks to them: