/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.advanced;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoText;
public class AlgoContinuedFraction extends AlgoElement {
private GeoNumberValue num; // input
private GeoNumberValue level; // input
private GeoText text; // output
private GeoBoolean shorthand;
private static final int MAX_QUOTIENTS = 15;
private long denominators[] = new long[MAX_QUOTIENTS];
private StringBuilder sb = new StringBuilder();
private boolean dotsNeeded;
public AlgoContinuedFraction(Construction cons, String label,
GeoNumberValue num, GeoNumberValue level, GeoBoolean shorthand) {
this(cons, num, level, shorthand);
text.setLabel(label);
}
AlgoContinuedFraction(Construction cons, GeoNumberValue num,
GeoNumberValue level, GeoBoolean shorthand) {
super(cons);
this.num = num;
this.level = level;
this.shorthand = shorthand;
text = new GeoText(cons);
text.setLaTeX(true, false);
text.setIsTextCommand(true); // stop editing as text
setInputOutput();
compute();
}
@Override
public Commands getClassName() {
return Commands.ContinuedFraction;
}
@Override
protected void setInputOutput() {
int inputLength = 1 + (level == null ? 0 : 1)
+ (shorthand == null ? 0 : 1);
input = new GeoElement[inputLength];
input[0] = num.toGeoElement();
int shorthandPos = 1;
if (level != null) {
shorthandPos = 2;
input[1] = level.toGeoElement();
}
if (shorthand != null) {
input[shorthandPos] = shorthand;
}
setOutputLength(1);
setOutput(0, text);
setDependencies(); // done by AlgoElement
}
public GeoText getResult() {
return text;
}
@Override
public final void compute() {
StringTemplate tpl = text.getStringTemplate();
if (num.isDefined() && (level == null || level.isDefined())) {
int maxSteps = level == null ? 0 : (int) level.getDouble();
int steps = decimalToFraction(num.getDouble(),
Kernel.STANDARD_PRECISION, denominators, maxSteps);
if (steps < 1) {
text.setUndefined();
return;
}
if (steps == 1) { // integer
text.setTextString(
kernel.format(Math.round(num.getDouble()), tpl));
} else {
if (shorthand == null || !shorthand.getBoolean()) {
appendLongLatex(steps, tpl);
} else {
sb.setLength(0);
if (num.getDouble() < 0) {
sb.append('-');
}
sb.append('[');
sb.append(kernel.format(denominators[0], tpl));
sb.append(';');
for (int i = 1; i < steps - 1; i++) {
sb.append(kernel.format(denominators[i], tpl));
sb.append(",");
}
sb.append(kernel.format(denominators[steps - 1], tpl));
if (dotsNeeded) {
sb.append(",\\ldots");
}
sb.append(']');
text.setTextString(sb.toString());
}
}
text.setLaTeX(true, false);
} else {
text.setLaTeX(false, false);
text.setTextString("?");
}
}
private void appendLongLatex(int steps, StringTemplate tpl) {
sb.setLength(0);
int start = 0;
if (num.getDouble() < 0) {
sb.append('-');
sb.append(kernel.format(denominators[0], tpl));
sb.append("-\\frac{1}{");
start = 1;
}
for (int i = start; i < steps - 1; i++) {
sb.append(kernel.format(denominators[i], tpl));
sb.append("+\\frac{1}{");
}
sb.append(kernel.format(denominators[steps - 1], tpl));
if (dotsNeeded) {
sb.append("+\\cdots");
}
// checkDecimalFraction() needed for eg
// FractionText[20.0764]
for (int i = 0; i < steps - 1; i++) {
sb.append("}");
}
// Log.debug(sb.toString());
text.setTextString(sb.toString());
}
/*
* Algorithm To Convert A Decimal To A Fraction by John Kennedy Mathematics
* Department Santa Monica College 1900 Pico Blvd. Santa Monica, CA 90405
* http://homepage.smc.edu/kennedy_john/DEC2FRAC.PDF
*/
private int decimalToFraction(double dec, double AccuracyFactor,
long[] denom, int maxSteps) {
double FractionNumerator, FractionDenominator;
double Z;
double PreviousDenominator;
double ScratchValue;
if (Double.isNaN(dec)) {
return -1;
}
if (dec == Double.POSITIVE_INFINITY
|| dec == Double.NEGATIVE_INFINITY) {
return -1;
}
double decimal = Math.abs(dec);
// handles exact integers including 0
if (Math.abs(decimal - Math.floor(decimal)) < AccuracyFactor) {
denom[0] = (int) Math.floor(decimal);
return 1;
}
if (decimal < 1.0E-19) { // X = 0 already taken care of
denom[0] = 0;
return 2;
}
if (decimal > 1.0E19) {
denom[0] = 999999999;
return 1;
}
Z = decimal;
PreviousDenominator = 0.0;
FractionDenominator = 1.0;
int steps = 0;
dotsNeeded = true;
do {
denom[steps] = (long) Math.floor(Z);
Z = 1.0 / (Z - Math.floor(Z));
ScratchValue = FractionDenominator;
FractionDenominator = FractionDenominator * Math.floor(Z)
+ PreviousDenominator;
PreviousDenominator = ScratchValue;
FractionNumerator = Math.floor(decimal * FractionDenominator + 0.5); // Rounding
// Function
steps++;
// we are too close to integer, next step would be uncertain
if (Kernel.isEqual(Z, Math.floor(Z))) {
denom[steps] = (long) Math.floor(Z);
dotsNeeded = false;
steps++;
break;
}
// the approximation is within standard precision
if (Math.abs((decimal - (FractionNumerator
/ FractionDenominator))) <= AccuracyFactor) {
denom[steps] = (long) Math.floor(Z);
steps++;
break;
}
} while ((maxSteps == 0 || steps < maxSteps)
&& !Kernel.isEqual(Z, Math.floor(Z)) && steps < denom.length);
return steps;
}
@Override
public boolean isLaTeXTextCommand() {
return true;
}
}