viewCoefficients() {
+ return Collections.unmodifiableList(coefficients);
+ }
+
+ /**
+ * If this instance does not have any coefficients stored, this method
+ * returns the array {@code {1, 0}}. Otherwise, it returns a 2-element array
+ * containing the numerator followed by the denominator of the convergent
+ * corresponding to the last coefficient of this continued fraction. The
+ * convergent's denominator will always be positive.
+ * @return a representation of a convergent as described above
+ */
+ BigInteger[] getCurrentConvergent() {
+ return Arrays.copyOf(currentConvergent, 2);
+ }
+
+ /**
+ * If this instance does not have any coefficients stored, this method
+ * returns the array {@code {0, 1}}. If exactly one coefficient is stored,
+ * it returns the array {@code {1, 0}}. Otherwise, it returns a 2-element
+ * array containing the numerator followed by the denominator of the
+ * convergent corresponding to the second to last coefficient of this
+ * continued fraction. The convergent's denominator will always be positive.
+ * @return a representation of a convergent as described above
+ */
+ BigInteger[] getPreviousConvergent() {
+ return Arrays.copyOf(previousConvergent, 2);
+ }
+
+ /**
+ * Converts this continued fraction to a {@code BigFraction}.
+ * @return a {@code BigFraction} instance equivalent to this continued
+ * fraction, reduced to lowest terms
+ * @throws IllegalStateException if this instance does not have any
+ * coefficients stored
+ */
+ BigFraction toBigFraction() {
+ if (coefficients.isEmpty()) {
+ throw new IllegalStateException(COEFFICIENTS_EMPTY_ERROR_MESSAGE);
+ }
+ return BigFraction.of(currentConvergent[0], currentConvergent[1]);
+ }
+
+ /**
+ * Ascertains that an integer that is intended to be used as a coefficient
+ * other than the first one is positive, and throws an
+ * {@code IllegalArgumentException} with a corresponding error message if it
+ * is not.
+ * @param coefficient the prospective non-initial coefficient
+ * @throws NullPointerException if the argument is {@code null}
+ * @throws IllegalArgumentException if the argument is not positive
+ */
+ private static void requirePositiveCoefficient(BigInteger coefficient) {
+ if (coefficient.signum() != 1) {
+ throw new IllegalArgumentException("Only the initial coefficient may be non-positive: " + coefficient);
+ }
+ }
+
+ /**
+ * Generates an iterator that iterates over the coefficients of the
+ * simple continued fraction representation of the passed
+ * {@code BigFraction} object.
+ *
+ * If [a0; a1, a2, …] is
+ * the simple continued fraction representation of the argument, then the
+ * ({@code i+1})th invocation of the returned iterator's {@link Iterator#next()
+ * next()} method returns a 3-element {@code BigInteger} array
+ * {ai, p, q}, where {@code p} and {@code q}
+ * are integers such that {@code q > 0} and p/q = [ai;
+ * ai+1, ai+2, …].
+ *
+ * Note that the iterator returned by this method always generates the
+ * shorter of the two equivalent simple continued fraction representations
+ * of a rational number, i.e. the one where the last coefficient is not 1
+ * unless the represented number itself is 1 (in which case the generated
+ * sequence of coefficients will simply be [1;] rather than [0; 1]). For
+ * example, when passed 31/24, the iterator will generate the sequence
+ * [1; 3, 2, 3], and not the equivalent sequence [1; 3, 2, 2, 1].
+ * @param fraction the fraction the coefficients of whose simple continued
+ * fraction representation should be iterated over
+ * @return an {@code Iterator} as described above
+ * @throws NullPointerException if the argument is {@code null}
+ */
+ static Iterator coefficientsOf(BigFraction fraction) {
+ final BigInteger initialDividend;
+ final BigInteger initialDivisor;
+ if (fraction.getDenominator().signum() == -1) {
+ initialDividend = fraction.getNumerator().negate();
+ initialDivisor = fraction.getDenominator().negate();
+ } else {
+ initialDividend = fraction.getNumerator();
+ initialDivisor = fraction.getDenominator();
+ }
+
+ return new Iterator() {
+ private BigInteger dividend = initialDividend;
+ private BigInteger divisor = initialDivisor;
+
+ @Override
+ public boolean hasNext() {
+ return !divisor.equals(BigInteger.ZERO);
+ }
+
+ @Override
+ public BigInteger[] next() {
+ BigInteger[] quotientAndRemainder;
+ try {
+ quotientAndRemainder = dividend.divideAndRemainder(divisor);
+ } catch (ArithmeticException e) {
+ throw new NoSuchElementException();
+ }
+ //simulate floor function for negative quotients to ensure non-negative remainder
+ if (quotientAndRemainder[1].signum() == -1) {
+ quotientAndRemainder[0] = quotientAndRemainder[0].subtract(BigInteger.ONE);
+ quotientAndRemainder[1] = quotientAndRemainder[1].add(divisor);
+ }
+ BigInteger[] result = new BigInteger[]{quotientAndRemainder[0], dividend, divisor};
+ dividend = divisor;
+ divisor = quotientAndRemainder[1];
+ return result;
+ }
+ };
+ }
+}
diff --git a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
index 74aefc49b..794f00f87 100644
--- a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
+++ b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
@@ -75,12 +75,6 @@ public void testConstructor() {
} catch (ArithmeticException ignored) {
// expected
}
- try {
- BigFraction.from(2.0 * Integer.MAX_VALUE, 1.0e-5, 100000);
- Assertions.fail("Expecting ArithmeticException");
- } catch (ArithmeticException ignored) {
- // expected
- }
}
@Test
@@ -105,46 +99,87 @@ public void testDoubleConstructor() throws Exception {
// MATH-181
@Test
- public void testDigitLimitConstructor() throws Exception {
- assertFraction(2, 5, BigFraction.from(0.4, 9));
- assertFraction(2, 5, BigFraction.from(0.4, 99));
- assertFraction(2, 5, BigFraction.from(0.4, 999));
+ public void testDigitLimitConstructor() {
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(4d, BigInteger.ZERO)
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(-2d, BigInteger.valueOf(-1))
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(Double.NaN, BigInteger.valueOf(6))
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(Double.POSITIVE_INFINITY, BigInteger.valueOf(23456789012345L))
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(Double.NEGATIVE_INFINITY, BigInteger.ONE)
+ );
- assertFraction(3, 5, BigFraction.from(0.6152, 9));
- assertFraction(8, 13, BigFraction.from(0.6152, 99));
- assertFraction(510, 829, BigFraction.from(0.6152, 999));
- assertFraction(769, 1250, BigFraction.from(0.6152, 9999));
+ Assertions.assertThrows(NullPointerException.class,
+ () -> BigFraction.from(23d, null)
+ );
+
+ assertFraction(2, 5, BigFraction.from(0.4, BigInteger.valueOf(9)));
+ assertFraction(2, 5, BigFraction.from(0.4, BigInteger.valueOf(99)));
+ assertFraction(2, 5, BigFraction.from(0.4, BigInteger.valueOf(999)));
+ assertFraction(3602879701896397L, 1L << 53, BigFraction.from(0.4, BigInteger.valueOf(1L << 54)));
+
+ assertFraction(5, 8, BigFraction.from(0.6152, BigInteger.valueOf(9)));
+ assertFraction(8, 13, BigFraction.from(0.6152, BigInteger.valueOf(99)));
+ assertFraction(510, 829, BigFraction.from(0.6152, BigInteger.valueOf(999)));
+ assertFraction(769, 1250, BigFraction.from(0.6152, BigInteger.valueOf(9999)));
+ assertFraction(2770614490758329L, 1L << 52, BigFraction.from(0.6152, BigInteger.valueOf(1L << 52)));
// MATH-996
- assertFraction(1, 2, BigFraction.from(0.5000000001, 10));
- }
+ assertFraction(1, 2, BigFraction.from(0.5000000001, BigInteger.valueOf(10)));
- // MATH-1029
- @Test
- public void testPositiveValueOverflow() {
- Assertions.assertThrows(ArithmeticException.class,
- () -> assertFraction((long) 1e10, 1, BigFraction.from(1e10, 1000))
- );
+ assertFraction(-5, 1, BigFraction.from(Math.nextDown(-4.5), BigInteger.ONE));
+ assertFraction(-4, 1, BigFraction.from(Math.nextUp(-4.5), BigInteger.ONE));
+ {
+ BigFraction f = BigFraction.from(-4.5, BigInteger.ONE);
+ Assertions.assertTrue(f.equals(BigFraction.of(-5)) || f.equals(BigFraction.of(-4)), "Expected: -5/1 or -4/1; Actual: " + f.toString());
+ }
}
- // MATH-1029
@Test
- public void testNegativeValueOverflow() {
- Assertions.assertThrows(ArithmeticException.class,
- () -> assertFraction((long) -1e10, 1, BigFraction.from(-1e10, 1000))
+ public void testEpsilonLimitConstructor() {
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(0.13579d, Double.NaN, 10)
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(Double.NaN, 1.0e-5, -10)
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(Double.POSITIVE_INFINITY, 1.0e-4, Integer.MAX_VALUE)
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> BigFraction.from(Double.NEGATIVE_INFINITY, 1.0e-3, 1)
);
- }
- @Test
- public void testEpsilonLimitConstructor() throws Exception {
assertFraction(2, 5, BigFraction.from(0.4, 1.0e-5, 100));
assertFraction(3, 5, BigFraction.from(0.6152, 0.02, 100));
assertFraction(8, 13, BigFraction.from(0.6152, 1.0e-3, 100));
- assertFraction(251, 408, BigFraction.from(0.6152, 1.0e-4, 100));
+ assertFraction(171, 278, BigFraction.from(0.6152, 1.0e-4, 100));
assertFraction(251, 408, BigFraction.from(0.6152, 1.0e-5, 100));
assertFraction(510, 829, BigFraction.from(0.6152, 1.0e-6, 100));
assertFraction(769, 1250, BigFraction.from(0.6152, 1.0e-7, 100));
+
+ assertFraction(1, 3, BigFraction.from(1d / 3d, 0x0.6P-54, -1));
+ assertFraction(3099251356470019L, 9297754069410058L, BigFraction.from(1d / 3d, 0x0.5P-54, -1));
+
+ assertFraction(1, 2, BigFraction.from(15d / 32d, 1d / 32d, -5));
+ assertFraction(3, 4, BigFraction.from(49d / 64d, 1d / 64d, -5));
+ assertFraction(1, 2, BigFraction.from(13d / 32d, 3d / 32d, 2));
+ assertFraction(5, 16, BigFraction.from(5147d / 0x1P14, 27d / 0x1P14, 3));
+
+ assertFraction(-5, 1, BigFraction.from(Math.nextDown(-4.5), Double.POSITIVE_INFINITY, -1));
+ assertFraction(-5, 1, BigFraction.from(Math.nextDown(-4.5), Double.NEGATIVE_INFINITY, -1));
+ {
+ BigFraction f = BigFraction.from(-4.5, 0.5, -1);
+ Assertions.assertTrue(f.equals(BigFraction.of(-5)) || f.equals(BigFraction.of(-4)), "Expected: -5/1 or -4/1; Actual: " + f.toString());
+ }
}
@Test
@@ -500,7 +535,7 @@ public void testMath340() {
public void testSerial() {
BigFraction[] fractions = {
BigFraction.of(3, 4), BigFraction.ONE, BigFraction.ZERO,
- BigFraction.of(17), BigFraction.from(Math.PI, 1000),
+ BigFraction.of(17), BigFraction.from(Math.PI, BigInteger.valueOf(1000)),
BigFraction.of(-5, 2)
};
for (BigFraction fraction : fractions) {
diff --git a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/SimpleContinuedFractionTest.java b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/SimpleContinuedFractionTest.java
new file mode 100644
index 000000000..60f8f0530
--- /dev/null
+++ b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/SimpleContinuedFractionTest.java
@@ -0,0 +1,177 @@
+package org.apache.commons.numbers.fraction;
+
+import java.math.BigInteger;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class SimpleContinuedFractionTest {
+
+ private static void assertConvergent(long expectedNumerator, long expectedDenominator, BigInteger[] actual) {
+ Assertions.assertArrayEquals(
+ toBigIntegerArray(expectedNumerator, expectedDenominator),
+ actual
+ );
+ }
+
+ private static BigInteger[] toBigIntegerArray(long... arr) {
+ BigInteger[] result = new BigInteger[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ result[i] = BigInteger.valueOf(arr[i]);
+ }
+ return result;
+ }
+
+ @Test
+ void testAddCoefficient() {
+ final SimpleContinuedFraction testSubject = new SimpleContinuedFraction();
+ List actualCoefficients = testSubject.viewCoefficients();
+
+ testSubject.addCoefficient(BigInteger.ONE);
+ assertConvergent(1, 1, testSubject.getCurrentConvergent());
+ assertConvergent(1, 0, testSubject.getPreviousConvergent());
+
+ testSubject.addCoefficient(BigInteger.valueOf(2));
+ assertConvergent(3, 2, testSubject.getCurrentConvergent());
+ assertConvergent(1, 1, testSubject.getPreviousConvergent());
+
+ testSubject.addCoefficient(BigInteger.valueOf(3));
+ assertConvergent(10, 7, testSubject.getCurrentConvergent());
+ assertConvergent(3, 2, testSubject.getPreviousConvergent());
+
+ Assertions.assertThrows(NullPointerException.class,
+ () -> testSubject.addCoefficient(null)
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> testSubject.addCoefficient(BigInteger.valueOf(-4))
+ );
+ Assertions.assertEquals(
+ Arrays.asList(BigInteger.ONE, BigInteger.valueOf(2), BigInteger.valueOf(3)),
+ actualCoefficients
+ );
+ }
+
+ @Test
+ void testSetLastCoefficient() {
+ final SimpleContinuedFraction testSubject = new SimpleContinuedFraction();
+ List actualCoefficients = testSubject.viewCoefficients();
+
+ Assertions.assertThrows(IllegalStateException.class,
+ () -> testSubject.setLastCoefficient(BigInteger.ONE)
+ );
+
+ testSubject.addCoefficient(BigInteger.ONE);
+ BigInteger oldCoefficient = testSubject.setLastCoefficient(BigInteger.valueOf(-1));
+ Assertions.assertEquals(BigInteger.ONE, oldCoefficient);
+ assertConvergent(-1, 1, testSubject.getCurrentConvergent());
+ assertConvergent(1, 0, testSubject.getPreviousConvergent());
+
+ testSubject.addCoefficient(BigInteger.valueOf(2));
+ oldCoefficient = testSubject.setLastCoefficient(BigInteger.valueOf(3));
+ Assertions.assertEquals(BigInteger.valueOf(2), oldCoefficient);
+ assertConvergent(-2, 3, testSubject.getCurrentConvergent());
+ assertConvergent(-1, 1, testSubject.getPreviousConvergent());
+
+ Assertions.assertThrows(NullPointerException.class,
+ () -> testSubject.setLastCoefficient(null)
+ );
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> testSubject.setLastCoefficient(BigInteger.valueOf(-3))
+ );
+ Assertions.assertEquals(
+ Arrays.asList(BigInteger.valueOf(-1), BigInteger.valueOf(3)),
+ actualCoefficients
+ );
+ }
+
+ @Test
+ void testRemoveLastCoefficient() {
+ final SimpleContinuedFraction testSubject = new SimpleContinuedFraction();
+ testSubject.addCoefficient(BigInteger.ZERO);
+ testSubject.addCoefficient(BigInteger.valueOf(4));
+ testSubject.addCoefficient(BigInteger.valueOf(5));
+ testSubject.addCoefficient(BigInteger.valueOf(2));
+
+ BigInteger removedCoefficient = testSubject.removeLastCoefficient();
+ Assertions.assertEquals(BigInteger.valueOf(2), removedCoefficient);
+ assertConvergent(5, 21, testSubject.getCurrentConvergent());
+ assertConvergent(1, 4, testSubject.getPreviousConvergent());
+
+ removedCoefficient = testSubject.removeLastCoefficient();
+ Assertions.assertEquals(BigInteger.valueOf(5), removedCoefficient);
+ assertConvergent(1, 4, testSubject.getCurrentConvergent());
+ assertConvergent(0, 1, testSubject.getPreviousConvergent());
+
+ removedCoefficient = testSubject.removeLastCoefficient();
+ Assertions.assertEquals(BigInteger.valueOf(4), removedCoefficient);
+ assertConvergent(0, 1, testSubject.getCurrentConvergent());
+ assertConvergent(1, 0, testSubject.getPreviousConvergent());
+
+ removedCoefficient = testSubject.removeLastCoefficient();
+ Assertions.assertEquals(BigInteger.ZERO, removedCoefficient);
+
+ Assertions.assertThrows(IllegalStateException.class,
+ testSubject::removeLastCoefficient
+ );
+
+ Assertions.assertTrue(testSubject.viewCoefficients().isEmpty());
+ }
+
+ @Test
+ void testConvergentAccessorsWithEmptyInstance() {
+ final SimpleContinuedFraction testSubject = new SimpleContinuedFraction();
+
+ assertConvergent(1, 0, testSubject.getCurrentConvergent());
+ assertConvergent(0, 1, testSubject.getPreviousConvergent());
+ }
+
+ @Test
+ void testToBigFraction() {
+ final SimpleContinuedFraction testSubject = new SimpleContinuedFraction();
+
+ Assertions.assertThrows(IllegalStateException.class,
+ testSubject::toBigFraction
+ );
+
+ testSubject.addCoefficient(BigInteger.valueOf(3));
+ Assertions.assertEquals(BigFraction.of(3, 1), testSubject.toBigFraction());
+
+ testSubject.addCoefficient(BigInteger.valueOf(2));
+ Assertions.assertEquals(BigFraction.of(7, 2), testSubject.toBigFraction());
+ }
+
+ @Test
+ void testCoefficientsOf() {
+ Assertions.assertThrows(NullPointerException.class,
+ () -> SimpleContinuedFraction.coefficientsOf(null)
+ );
+
+ final Iterator coefficientsIterator = SimpleContinuedFraction.coefficientsOf(BigFraction.of(-415, 93));
+ BigInteger[] expectedCoefficients = toBigIntegerArray(-5, 1, 1, 6, 7);
+ BigFraction[] expectedFractions = new BigFraction[]{
+ BigFraction.of(-415, 93),
+ BigFraction.of(93, 50),
+ BigFraction.of(50, 43),
+ BigFraction.of(43, 7),
+ BigFraction.of(7, 1)
+ };
+
+ for (int i = 0; i < expectedCoefficients.length; i++) {
+ Assertions.assertTrue(coefficientsIterator.hasNext());
+ BigInteger[] next = coefficientsIterator.next();
+ Assertions.assertEquals(3, next.length);
+ Assertions.assertEquals(expectedCoefficients[i], next[0]);
+ Assertions.assertEquals(1, next[2].signum());
+ Assertions.assertEquals(expectedFractions[i], BigFraction.of(next[1], next[2]));
+ }
+ Assertions.assertFalse(coefficientsIterator.hasNext());
+ Assertions.assertThrows(NoSuchElementException.class,
+ coefficientsIterator::next
+ );
+ }
+}