ConvergentsIterator.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This is not the original file distributed by the Apache Software Foundation
* It has been modified by the Hipparchus project
*/
package org.hipparchus.fraction;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalStateException;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.Pair;
import org.hipparchus.util.Precision;
/**
* Generator for convergents.
*/
class ConvergentsIterator {
/** Unused constructor.
*/
private ConvergentsIterator() {
} // static use only
/** Container for one convergent step. */
static class ConvergenceStep {
/** Numerator of previous convergent. */
private final long p0;
/** Denominator of previous convergent. */
private final long q0;
/** Numerator of current convergent. */
private final long p1;
/** Denominator of current convergent. */
private final long q1;
/** Remainder of current convergent. */
private final double r1;
private ConvergenceStep(final long p0, final long q0, final long p1, final long q1, final double r1) {
this.p0 = p1;
this.q0 = q1;
final long a1 = (long) FastMath.floor(r1);
try {
this.p1 = FastMath.addExact(Math.multiplyExact(a1, p1), p0);
this.q1 = FastMath.addExact(Math.multiplyExact(a1, q1), q0);
this.r1 = 1.0 / (r1 - a1);
} catch (ArithmeticException e) { // unlike the name implies FastMath's multiplyExact() is slower
throw new MathIllegalStateException(e, LocalizedCoreFormats.FRACTION_CONVERSION_OVERFLOW, r1, p1, q1);
}
}
/** Builder from a double value.
* @param value value to approximate
* @return first step in approximation
*/
public static ConvergenceStep start(double value) {
return new ConvergenceStep(0, 1, 1, 0, value);
}
/** Compute next step in convergence.
* @return next convergence step
*/
public ConvergenceStep next() {
return new ConvergenceStep(p0, q0, p1, q1, r1);
}
/** Get the numerator of current convergent.
* @return numerator of current convergent
*/
public long getNumerator() {
return p1;
}
/** Get the denominator of current convergent.
* @return denominator of current convergent
*/
public long getDenominator() {
return q1;
}
/** Compute double value of current convergent.
* @return double value of current convergent
*/
public double getFractionValue() {
return getNumerator() / (double) getDenominator();
}
/** Convert convergent to string representation.
* @return string representation of convergent
*/
@Override
public String toString() {
return getNumerator() + "/" + getDenominator();
}
}
/**
* Returns the last element of the series of convergent-steps to approximate the
* given value.
* <p>
* The series terminates either at the first step that satisfies the given
* {@code convergenceTest} or after at most {@code maxConvergents} elements. The
* returned Pair consists of that terminal step and a {@link Boolean} that
* indicates if it satisfies the given convergence tests. If the returned pair's
* value is {@code false} the element at position {@code maxConvergents} was
* examined but failed to satisfy the {@code convergenceTest}.
*
* @param value value to approximate
* @param maxConvergents maximum number of convergents to examine
* @param convergenceTests the test if the series has converged at a step
* @return the pair of last element of the series of convergents and a boolean
* indicating if that element satisfies the specified convergent test
*/
static Pair<ConvergenceStep, Boolean> convergent(double value, int maxConvergents,
Predicate<ConvergenceStep> convergenceTests) {
ConvergenceStep step = ConvergenceStep.start(value);
for (int i = 1; i < maxConvergents; i++) { // start performs first iteration
if (convergenceTests.test(step)) {
return Pair.create(step, Boolean.TRUE);
}
step = step.next();
}
return Pair.create(step, convergenceTests.test(step));
}
/**
* Generate a {@link Stream stream} of {@code ConvergenceStep convergent-steps}
* from a real number.
*
* @param value value to approximate
* @param maxConvergents maximum number of convergent steps.
* @return stream of {@link ConvergenceStep convergent-steps} approximating
* {@code value}
*/
static Stream<ConvergenceStep> convergents(final double value, final int maxConvergents) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<ConvergenceStep>() {
/** Next convergent. */
private ConvergenceStep next = ConvergenceStep.start(value);
/** {@inheritDoc} */
@Override
public boolean hasNext() {
return next != null;
}
/** {@inheritDoc} */
@Override
public ConvergenceStep next() {
final ConvergenceStep ret = next;
if (Precision.equals(ret.getFractionValue(), value, 1)) {
next = null; // stop if precision has been reached
} else {
next = next.next();
}
return ret;
}
}, Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.ORDERED),
false).
limit(maxConvergents);
}
}