1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-25 16:15:19 +00:00

Compare commits

...

9 Commits

12 changed files with 244 additions and 194 deletions

1
build.gradle Normal file → Executable file
View File

@@ -11,6 +11,7 @@ repositories {
} }
dependencies { dependencies {
compile group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
compile 'com.moandjiezana.toml:toml4j:0.7.1' compile 'com.moandjiezana.toml:toml4j:0.7.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8"
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'

View File

@@ -216,7 +216,7 @@ public class AbacusController implements PluginListener {
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory = Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>(); param -> new CopyableCell<>();
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory = Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() { param -> new CheckBoxListCell<>(ToggleablePlugin::getEnabledProperty, new StringConverter<ToggleablePlugin>() {
@Override @Override
public String toString(ToggleablePlugin object) { public String toString(ToggleablePlugin object) {
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1); return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
@@ -224,7 +224,7 @@ public class AbacusController implements PluginListener {
@Override @Override
public ToggleablePlugin fromString(String string) { public ToggleablePlugin fromString(String string) {
return new ToggleablePlugin(true, string); return new ToggleablePlugin(string, true);
} }
}); });
@@ -243,11 +243,11 @@ public class AbacusController implements PluginListener {
enabledPluginView.setItems(enabledPlugins); enabledPluginView.setItems(enabledPlugins);
enabledPluginView.setCellFactory(pluginCellFactory); enabledPluginView.setCellFactory(pluginCellFactory);
inputColumn.setCellFactory(cellFactory); inputColumn.setCellFactory(cellFactory);
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty()); inputColumn.setCellValueFactory(cell -> cell.getValue().getInputProperty());
parsedColumn.setCellFactory(cellFactory); parsedColumn.setCellFactory(cellFactory);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty()); parsedColumn.setCellValueFactory(cell -> cell.getValue().getParsedProperty());
outputColumn.setCellFactory(cellFactory); outputColumn.setCellFactory(cellFactory);
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty()); outputColumn.setCellValueFactory(cell -> cell.getValue().getOutputProperty());
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true); if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
}); });
@@ -343,8 +343,8 @@ public class AbacusController implements PluginListener {
numberImplementationBox.getSelectionModel().select(toSelect); numberImplementationBox.getSelectionModel().select(toSelect);
for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) { for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) {
String fullName = pluginClass.getName(); String fullName = pluginClass.getName();
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName); ToggleablePlugin plugin = new ToggleablePlugin(fullName, !disabledPlugins.contains(fullName));
plugin.enabledProperty().addListener(e -> changesMade = true); plugin.getEnabledProperty().addListener(e -> changesMade = true);
enabledPlugins.add(plugin); enabledPlugins.add(plugin);
} }
functionList.addAll(manager.getAllFunctions()); functionList.addAll(manager.getAllFunctions());

View File

@@ -1,97 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* The data model used for storing history entries.
*/
public class HistoryModel {
/**
* The property used for displaying the column
* for the user input.
*/
private final StringProperty input;
/**
* The property used for displaying the column
* that contains the parsed input.
*/
private final StringProperty parsed;
/**
* The property used for displaying the column
* that contains the program output.
*/
private final StringProperty output;
/**
* Creates a new history model with the given variables.
*
* @param input the user input
* @param parsed the parsed input
* @param output the program output.
*/
public HistoryModel(String input, String parsed, String output) {
this.input = new SimpleStringProperty();
this.parsed = new SimpleStringProperty();
this.output = new SimpleStringProperty();
this.input.setValue(input);
this.parsed.setValue(parsed);
this.output.setValue(output);
}
/**
* Gets the input property.
*
* @return the input property.
*/
public StringProperty inputProperty() {
return input;
}
/**
* Gets the input.
*
* @return the input.
*/
public String getInput() {
return input.get();
}
/**
* Gets the parsed input property.
*
* @return the parsed input property.
*/
public StringProperty parsedProperty() {
return parsed;
}
/**
* Gets the parsed input.
*
* @return the parsed input.
*/
public String getParsed() {
return parsed.get();
}
/**
* Gets the output property.
*
* @return the output property.
*/
public StringProperty outputProperty() {
return output;
}
/**
* Gets the program output.
*
* @return the output.
*/
public String getOutput() {
return output.get();
}
}

View File

@@ -1,60 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
* Class that represents an entry in the plugin check box list.
* The changes from this property are written to the config on application.
*/
public class ToggleablePlugin {
/**
* The property that determines whether the plugin will be enabled.
*/
private final BooleanProperty enabled;
/**
* The name of the class this entry toggles.
*/
private final String className;
/**
* Creates a new toggleable plugin with the given properties.
*
* @param enabled the enabled / disabled state at the beginning.
* @param className the name of the class this plugin toggles.
*/
public ToggleablePlugin(boolean enabled, String className) {
this.enabled = new SimpleBooleanProperty();
this.enabled.setValue(enabled);
this.className = className;
}
/**
* Gets the enabled property of this plugin.
*
* @return the enabled property.
*/
public BooleanProperty enabledProperty() {
return enabled;
}
/**
* Checks if this plugin entry should be enabled.
*
* @return whether this plugin will be enabled.
*/
public boolean isEnabled() {
return enabled.get();
}
/**
* Gets the class name this plugin toggles.
*
* @return the class name that should be disabled.
*/
public String getClassName() {
return className;
}
}

View File

@@ -128,5 +128,9 @@ public class NaiveNumber extends NumberInterface {
return Double.toString(Math.round(value * shiftBy) / shiftBy); return Double.toString(Math.round(value * shiftBy) / shiftBy);
} }
@Override
public NumberInterface getMaxError(){
return new NaiveNumber(Math.pow(10, -18));
}
} }

View File

@@ -255,4 +255,11 @@ public abstract class NumberInterface {
return promoteToInternal(toClass); return promoteToInternal(toClass);
} }
/**
* Returns the smallest error this instance can tolerate depending
* on its precision and value.
* @return the smallest error that should be permitted in calculations.
*/
public abstract NumberInterface getMaxError();
} }

View File

@@ -1,7 +1,8 @@
package org.nwapw.abacus.number; package org.nwapw.abacus.number;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.BigInteger;
import java.math.MathContext;
/** /**
* A number that uses a BigDecimal to store its value, * A number that uses a BigDecimal to store its value,
@@ -22,6 +23,21 @@ public class PreciseNumber extends NumberInterface {
*/ */
public static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN); public static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN);
/**
* The number of extra significant figures kept in calculations before rounding for output.
*/
private static int numExtraInternalSigFigs = 15;
/**
* MathContext that is used when rounding a number prior to output.
*/
private static MathContext outputContext = new MathContext(50);
/**
* MathContext that is actually used in calculations.
*/
private static MathContext internalContext = new MathContext(outputContext.getPrecision()+numExtraInternalSigFigs);
/** /**
* The value of the PreciseNumber. * The value of the PreciseNumber.
*/ */
@@ -46,9 +62,18 @@ public class PreciseNumber extends NumberInterface {
this.value = value; this.value = value;
} }
/**
* Constructs a precise number from the given BigInteger.
*
* @param value a BigInteger object representing the value of the number.
*/
public PreciseNumber(BigInteger value) {
this.value = new BigDecimal(value);
}
@Override @Override
public int getMaxPrecision() { public int getMaxPrecision() {
return 65; return internalContext.getPrecision();
} }
@Override @Override
@@ -58,7 +83,7 @@ public class PreciseNumber extends NumberInterface {
@Override @Override
public NumberInterface divideInternal(NumberInterface divisor) { public NumberInterface divideInternal(NumberInterface divisor) {
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP)); return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, internalContext));
} }
@Override @Override
@@ -147,7 +172,11 @@ public class PreciseNumber extends NumberInterface {
@Override @Override
public String toString() { public String toString() {
BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP); return value.round(outputContext).toString();
return rounded.stripTrailingZeros().toPlainString(); }
@Override
public NumberInterface getMaxError(){
return new PreciseNumber(value.ulp()).multiplyInternal(TEN.intPowInternal(value.precision()-internalContext.getPrecision()));
} }
} }

View File

@@ -0,0 +1,115 @@
package org.nwapw.abacus.number;
import org.apache.commons.math3.fraction.BigFraction;
import org.apache.commons.math3.fraction.Fraction;
import java.math.BigInteger;
public class RationalNumber extends NumberInterface{
static final RationalNumber ONE = new RationalNumber(BigFraction.ONE);
/**
* The value of the number.
*/
private BigFraction value;
/**
* Constructs a new instance with the given value.
* @param value
*/
public RationalNumber(BigFraction value){
this.value = value;
}
@Override
public int getMaxPrecision() {
return 0;
}
@Override
protected NumberInterface multiplyInternal(NumberInterface multiplier) {
return new RationalNumber(value.multiply(((RationalNumber)multiplier).value));
}
@Override
protected NumberInterface divideInternal(NumberInterface divisor) {
return new RationalNumber(value.divide(((RationalNumber)divisor).value));
}
@Override
protected NumberInterface addInternal(NumberInterface summand) {
return new RationalNumber(value.add(((RationalNumber)summand).value));
}
@Override
protected NumberInterface subtractInternal(NumberInterface subtrahend) {
return new RationalNumber(value.subtract(((RationalNumber)subtrahend).value));
}
@Override
protected NumberInterface negateInternal() {
return new RationalNumber(value.negate());
}
@Override
protected NumberInterface intPowInternal(int exponent) {
return new RationalNumber(value.pow(exponent));
}
@Override
public int compareTo(NumberInterface number) {
return value.compareTo(((RationalNumber)number).value);
}
@Override
public int signum() {
return value.getNumerator().signum();
}
@Override
protected NumberInterface ceilingInternal() {
if(value.getNumeratorAsInt() != 1){
return floorInternal().add(ONE);
}
return this;
}
@Override
protected NumberInterface floorInternal() {
BigInteger floor = value.bigDecimalValue().toBigInteger();
if(value.compareTo(BigFraction.ZERO) < 0 && value.getDenominatorAsInt() != 1){
floor = floor.subtract(BigInteger.ONE);
}
return new RationalNumber(new BigFraction(floor));
}
@Override
protected NumberInterface fractionalPartInternal() {
return this.subtractInternal(floorInternal());
}
@Override
public int intValue() {
return 0;
}
@Override
protected NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
return null;
}
@Override
public NumberInterface getMaxError() {
return toPreciseNumber().getMaxError();
}
@Override
public String toString(){
return toPreciseNumber().toString();
}
PreciseNumber toPreciseNumber(){
return (PreciseNumber) new PreciseNumber(value.getNumerator()).divideInternal(new PreciseNumber(value.getDenominator()));
}
}

View File

@@ -188,7 +188,7 @@ public class StandardPlugin extends Plugin {
*/ */
private NumberInterface getLogPartialSum(NumberInterface x) { private NumberInterface getLogPartialSum(NumberInterface x) {
NumberInterface maxError = getMaxError(x); NumberInterface maxError = x.getMaxError();
x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1). x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x; NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1; int n = 1;
@@ -207,7 +207,7 @@ public class StandardPlugin extends Plugin {
* @return the value of log(2) with the appropriate precision. * @return the value of log(2) with the appropriate precision.
*/ */
private NumberInterface getLog2(NumberInterface number) { private NumberInterface getLog2(NumberInterface number) {
NumberInterface maxError = getMaxError(number); NumberInterface maxError = number.getMaxError();
//NumberInterface errorBound = fromInt(number.getClass(), 1); //NumberInterface errorBound = fromInt(number.getClass(), 1);
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n) //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n. //In the following, a=1/3^n, b=1/4^n, c = 1/n.
@@ -301,16 +301,11 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = getMaxError(params[0]); NumberInterface maxError = params[0].getMaxError();
int n = 0; int n = 0;
if (params[0].signum() <= 0) { if (params[0].signum() < 0) {
NumberInterface currentTerm = NaiveNumber.ONE.promoteTo(params[0].getClass()), sum = currentTerm; NumberInterface[] negatedParams = {params[0].negate()};
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) { return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams));
n++;
currentTerm = currentTerm.multiply(params[0]).divide((new NaiveNumber(n)).promoteTo(params[0].getClass()));
sum = sum.add(currentTerm);
}
return sum;
} else { } else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!. //We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality. //right and left refer to lhs and rhs in the above inequality.
@@ -352,7 +347,7 @@ public class StandardPlugin extends Plugin {
return NaiveNumber.ONE.promoteTo(params[1].getClass()); return NaiveNumber.ONE.promoteTo(params[1].getClass());
//Detect integer bases: //Detect integer bases:
if(params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0 if(params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0 && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0){ && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0){
NumberInterface[] newParams = {params[0], params[1].fractionalPart()}; NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams)); return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams));
@@ -476,16 +471,6 @@ public class StandardPlugin extends Plugin {
return sum; return sum;
} }
/**
* Returns the maximum error based on the precision of the class of number.
*
* @param number Any instance of the NumberInterface in question (should return an appropriate precision).
* @return the maximum error.
*/
private static NumberInterface getMaxError(NumberInterface number) {
return fromInt(number.getClass(), 10).intPow(-number.getMaxPrecision());
}
/** /**
* A factorial function that uses memoization for each number class; it efficiently * A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers. * computes factorials of non-negative integers.
@@ -517,7 +502,7 @@ public class StandardPlugin extends Plugin {
*/ */
private static NumberInterface sinTaylor(NumberInterface x) { private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x; NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = getMaxError(x); NumberInterface maxError = x.getMaxError();
int n = 1; int n = 1;
do { do {
n += 2; n += 2;

View File

@@ -0,0 +1,32 @@
package org.nwapw.abacus.fx
import javafx.beans.property.SimpleStringProperty
/**
* A model representing an input / output in the calculator.
*
* The HistoryModel class stores a record of a single user-provided input,
* its parsed form as it was interpreted by the calculator, and the output
* that was provided by the calculator. These are represented as properties
* to allow easy access by JavaFX cells.
*
* @param input the user input
* @param parsed the parsed version of the input.
* @param output the output string.
*/
class HistoryModel(input: String, parsed: String, output: String){
/**
* The property that holds the input.
*/
val inputProperty = SimpleStringProperty(input)
/**
* The property that holds the parsed input.
*/
val parsedProperty = SimpleStringProperty(parsed)
/**
* The property that holds the output.
*/
val outputProperty = SimpleStringProperty(output)
}

View File

@@ -0,0 +1,31 @@
package org.nwapw.abacus.fx
import javafx.beans.property.SimpleBooleanProperty
/**
* A model representing a plugin that can be disabled or enabled.
*
* ToggleablePlugin is a model that is used to present to the user the option
* of disabling / enabling plugins. The class name in this plugin is stored if
* its "enabledPropery" is false, essentially blacklisting the plugin.
*
* @param className the name of the class that this model concerns.
* @param enabled whether or not the model should start enabled.
*/
class ToggleablePlugin (val className: String, enabled: Boolean) {
/**
* The property used to interact with JavaFX components.
*/
val enabledProperty = SimpleBooleanProperty(enabled)
/**
* Checks whether this plugin is currently enabled or not.
*
* @return true if it is enabled, false otherwise.
*/
fun isEnabled(): Boolean {
return enabledProperty.value
}
}

View File

@@ -88,8 +88,8 @@ public class CalculationTests {
public void testExp(){ public void testExp(){
testOutput("exp0", "exp(0)", "1"); testOutput("exp0", "exp(0)", "1");
testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247"); testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247");
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086"); testOutput("exp300", "exp(300)", "1.9424263952412559365842088360176992193662086");
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086"); testOutput("exp(-500)", "exp((500)`)", "7.1245764067412855315491573771227552469277568");
} }
@Test @Test
@@ -99,6 +99,9 @@ public class CalculationTests {
testOutput("2^1", "(2^1)", "2"); testOutput("2^1", "(2^1)", "2");
testOutput("2^-1", "(2^(1)`)", "0.5"); testOutput("2^-1", "(2^(1)`)", "0.5");
testOutput("2^50", "(2^50)", "112589990684262"); testOutput("2^50", "(2^50)", "112589990684262");
testOutput("7^(-sqrt2*17)", "(7^((sqrt(2)*17))`)", "4.81354609155297814551845300063563");
testEvalError("0^0", "(0^0)");
testEvalError("(-13)^.9999", "((13)`^0.9999)");
} }
} }