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

Compare commits

..

37 Commits

Author SHA1 Message Date
91978686e6 Merge branch 'master' into website-update 2017-09-11 19:06:02 -07:00
9a8d0afc19 Merge pull request #18 from DanilaFe/configuration-refactor
Refactor configuration
2017-09-11 19:05:52 -07:00
5aba5c350b Add comments to the new configuration classes. 2017-09-11 18:15:40 -07:00
21b7bd5e2b Move TOML code out of the configuration in core, and into fx. 2017-09-11 18:06:40 -07:00
dc4eee6342 Decrease the padding and margins on small screens. 2017-09-07 16:22:15 -07:00
6909f210d6 Add a features list to the landing page. 2017-09-07 16:22:08 -07:00
fd246f935c Merge pull request #12 from DanilaFe/no-null
Remove the use of null in Applicable calls.
2017-09-06 15:18:59 -07:00
6604af5b0f Merge branch 'master' into no-null 2017-09-06 15:16:55 -07:00
d49a763e8f Merge pull request #11 from DanilaFe/tree-value-variables
Add variables implemented via Tree Value Operators.
2017-09-06 15:11:06 -07:00
48a4d8adc2 Merge branch 'master' into tree-value-variables 2017-09-06 15:07:45 -07:00
5417b45106 Merge pull request #9 from DanilaFe/promotion-system
Implement a basic promotion system.
2017-09-06 15:05:22 -07:00
585cabc478 Add the ability for plugins to access variables, and add the operators. 2017-09-05 23:00:25 -07:00
28802cfed3 Remove the additional methods from the VariableDatabase. 2017-09-05 23:00:25 -07:00
428df8bfd3 Use the variable database for the number reducer. 2017-09-05 23:00:25 -07:00
146f3994ef Add the variable database. 2017-09-05 23:00:25 -07:00
daffdb6b42 Move Abacus core into Kotlin. 2017-09-05 23:00:25 -07:00
178f59ef7b Move the exception to the correct package. 2017-09-04 12:55:49 -07:00
61616a428a Fix tests that expected null from functions. 2017-09-04 12:55:49 -07:00
9c77fa8aeb Add a DomainException that avoids using null in functions. 2017-09-04 12:55:49 -07:00
9ddfeb02cf Fix not clearing an important map during reset. 2017-09-04 12:51:53 -07:00
bc4a26aafb Fix weird alignment. 2017-09-01 18:43:07 -07:00
1f6aa70230 Ensure PromotionManager clears its implementation cache. 2017-09-01 18:33:12 -07:00
e62722ce2f Add comments. 2017-09-01 18:32:41 -07:00
ce82fd56dd Remove newlines generated by IntelliJ's addition of @Nullable. 2017-09-01 18:11:19 -07:00
6a65e66935 Format code. 2017-09-01 18:07:48 -07:00
e172108476 Stop using fromInt and the promoteTo function. 2017-09-01 18:07:28 -07:00
2b700d3911 Require applicable interfaces to be passed an implementation they use. 2017-09-01 17:45:32 -07:00
f7c07ca04d Add promotion manager to Abacus. 2017-09-01 17:15:28 -07:00
ecb5139e70 Add a promotion manager to handle promotion. 2017-09-01 17:15:14 -07:00
453cd0ea77 Add type aliases for Kotlin and a simple extension function. 2017-09-01 17:14:54 -07:00
7a296e4e8b Add the ability to retrieve names of plugin implementations. 2017-09-01 17:14:11 -07:00
cbceee4abc Switch number implementation to using Strings. 2017-09-01 17:13:45 -07:00
8ea34b8f6e Merge pull request #8 from DanilaFe/new_image
Add a Logo.
Sorry Arthur, there's literally no reason to wait for approval on this one.
2017-08-30 20:04:12 -07:00
672252ef41 Merge branch 'master' into new_image 2017-08-30 15:35:35 -07:00
824f391fc7 Add the logo to GitHub pages. 2017-08-30 15:31:25 -07:00
63a160659a Merge pull request #7 from DanilaFe/tree-operators
Implement tree operators and functions.
2017-08-30 15:14:41 -07:00
879d09e5b8 Add a logo. 2017-08-30 15:14:08 -07:00
30 changed files with 830 additions and 641 deletions

View File

@@ -1,4 +1,3 @@
dependencies { dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
} }

View File

@@ -1,135 +0,0 @@
package org.nwapw.abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.parsing.ShuntingYardParser;
import org.nwapw.abacus.parsing.TreeBuilder;
import org.nwapw.abacus.plugin.NumberImplementation;
import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.NumberReducer;
import org.nwapw.abacus.tree.TreeNode;
/**
* The main calculator class. This is responsible
* for piecing together all of the components, allowing
* their interaction with each other.
*/
public class Abacus {
/**
* The default number implementation to be used if no other one is available / selected.
*/
public static final NumberImplementation DEFAULT_IMPLEMENTATION = StandardPlugin.IMPLEMENTATION_NAIVE;
/**
* The plugin manager responsible for
* loading and unloading plugins,
* and getting functions from them.
*/
private PluginManager pluginManager;
/**
* The reducer used to evaluate the tree.
*/
private NumberReducer numberReducer;
/**
* The configuration loaded from a file.
*/
private Configuration configuration;
/**
* The tree builder used to construct a tree
* from a string.
*/
private TreeBuilder treeBuilder;
/**
* Creates a new instance of the Abacus calculator.
*
* @param configuration the configuration object for this Abacus instance.
*/
public Abacus(Configuration configuration) {
pluginManager = new PluginManager(this);
numberReducer = new NumberReducer(this);
this.configuration = new Configuration(configuration);
LexerTokenizer lexerTokenizer = new LexerTokenizer();
ShuntingYardParser shuntingYardParser = new ShuntingYardParser();
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
pluginManager.addListener(shuntingYardParser);
pluginManager.addListener(lexerTokenizer);
}
/**
* Gets the current tree builder.
*
* @return the main tree builder in this abacus instance.
*/
public TreeBuilder getTreeBuilder() {
return treeBuilder;
}
/**
* Gets the current plugin manager,
*
* @return the plugin manager in this abacus instance.
*/
public PluginManager getPluginManager() {
return pluginManager;
}
/**
* Get the reducer that is responsible for transforming
* an expression into a number.
*
* @return the number reducer in this abacus instance.
*/
public NumberReducer getNumberReducer() {
return numberReducer;
}
/**
* Gets the configuration object associated with this instance.
*
* @return the configuration object.
*/
public Configuration getConfiguration() {
return configuration;
}
/**
* Parses a string into a tree structure using the main
* tree builder.
*
* @param input the input string to parse
* @return the resulting tree, null if the tree builder or the produced tree are null.
*/
public TreeNode parseString(String input) {
return treeBuilder.fromString(input);
}
/**
* Evaluates the given tree using the main
* number reducer.
*
* @param tree the tree to reduce, must not be null.
* @return the resulting number, or null of the reduction failed.
*/
public NumberInterface evaluateTree(TreeNode tree) {
return tree.reduce(numberReducer);
}
/**
* Creates a number from a string.
*
* @param numberString the string to create the number from.
* @return the resulting number.
*/
public NumberInterface numberFromString(String numberString) {
NumberImplementation toInstantiate =
pluginManager.numberImplementationFor(configuration.getNumberImplementation());
if (toInstantiate == null) toInstantiate = DEFAULT_IMPLEMENTATION;
return toInstantiate.instanceForString(numberString);
}
}

View File

@@ -1,159 +0,0 @@
package org.nwapw.abacus.config;
import com.moandjiezana.toml.Toml;
import com.moandjiezana.toml.TomlWriter;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* The configuration object that stores
* options that the user can change.
*/
public class Configuration {
/**
* The defaults TOML string.
*/
private static final String DEFAULT_CONFIG =
"numberImplementation = \"naive\"\n" +
"disabledPlugins = []";
/**
* The defaults TOML object, parsed from the string.
*/
private static final Toml DEFAULT_TOML = new Toml().read(DEFAULT_CONFIG);
/**
* The TOML writer used to write this configuration to a file.
*/
private static final TomlWriter TOML_WRITER = new TomlWriter();
/**
* The computation delay for which the thread can run without interruption.
*/
private double computationDelay = 0;
/**
* The implementation of the number that should be used.
*/
private String numberImplementation = "<default>";
/**
* The list of disabled plugins in this Configuration.
*/
private Set<String> disabledPlugins = new HashSet<>();
/**
* Creates a new configuration form the given configuration.
*
* @param copyFrom the configuration to copy.
*/
public Configuration(Configuration copyFrom) {
copyFrom(copyFrom);
}
/**
* Creates a new configuration with the given values.
*
* @param computationDelay the delay before the computation gets killed.
* @param numberImplementation the number implementation, like "naive" or "precise"
* @param disabledPlugins the list of disabled plugins.
*/
public Configuration(double computationDelay, String numberImplementation, String[] disabledPlugins) {
this.computationDelay = computationDelay;
this.numberImplementation = numberImplementation;
this.disabledPlugins.addAll(Arrays.asList(disabledPlugins));
}
/**
* Loads a configuration from a given file, keeping non-specified fields default.
*
* @param fromFile the file to load from.
*/
public Configuration(File fromFile) {
if (!fromFile.exists()) return;
copyFrom(new Toml(DEFAULT_TOML).read(fromFile).to(Configuration.class));
}
/**
* Copies the values from the given configuration into this one.
*
* @param otherConfiguration the configuration to copy from.
*/
public void copyFrom(Configuration otherConfiguration) {
this.computationDelay = otherConfiguration.computationDelay;
this.numberImplementation = otherConfiguration.numberImplementation;
this.disabledPlugins.addAll(otherConfiguration.disabledPlugins);
}
/**
* Saves this configuration to the given file, creating
* any directories that do not exist.
*
* @param file the file to save to.
*/
public void saveTo(File file) {
if (file.getParentFile() != null) file.getParentFile().mkdirs();
try {
TOML_WRITER.write(this, file);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets the value of this configuration as a string.
*
* @return the string that represents this configuration.
*/
public String asTomlString() {
return TOML_WRITER.write(this);
}
/**
* Gets the number implementation from this configuration.
*
* @return the number implementation.
*/
public String getNumberImplementation() {
return numberImplementation;
}
/**
* Sets the number implementation for the configuration
*
* @param numberImplementation the number implementation.
*/
public void setNumberImplementation(String numberImplementation) {
this.numberImplementation = numberImplementation;
}
/**
* Gets the list of disabled plugins.
*
* @return the list of disabled plugins.
*/
public Set<String> getDisabledPlugins() {
return disabledPlugins;
}
/**
* Gets the computation delay specified in the configuration.
*
* @return the computaton delay.
*/
public double getComputationDelay() {
return computationDelay;
}
/**
* Sets the computation delay.
*
* @param computationDelay the new computation delay.
*/
public void setComputationDelay(double computationDelay) {
this.computationDelay = computationDelay;
}
}

View File

@@ -0,0 +1,24 @@
package org.nwapw.abacus.function;
/**
* Exception thrown if the function parameters do not match
* requirements.
*/
public class DomainException extends RuntimeException {
/**
* Creates a new DomainException.
* @param reason the reason for which the exception is thrown.
*/
public DomainException(String reason) {
super(reason);
}
/**
* Creates a new DomainException with a default message.
*/
public DomainException(){
this("Domain Error");
}
}

View File

@@ -114,15 +114,6 @@ public class NaiveNumber extends NumberInterface {
return (int) value; return (int) value;
} }
@Override
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) return this;
else if (toClass == PreciseNumber.class) {
return new PreciseNumber(Double.toString(value));
}
return null;
}
public String toString() { public String toString() {
double shiftBy = Math.pow(10, 10); double shiftBy = Math.pow(10, 10);
return Double.toString(Math.round(value * shiftBy) / shiftBy); return Double.toString(Math.round(value * shiftBy) / shiftBy);

View File

@@ -236,29 +236,6 @@ public abstract class NumberInterface {
*/ */
public abstract int intValue(); public abstract int intValue();
/**
* Promotes this class to another number class.
*
* @param toClass the class to promote to.
* @return the resulting new instance.
*/
@Deprecated
protected abstract NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass);
/**
* Promotes this class to another number class. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param toClass the class to promote to.
* @return the resulting new instance.
*/
@Deprecated
public final NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
checkInterrupted();
return promoteToInternal(toClass);
}
/** /**
* Returns the smallest error this instance can tolerate depending * Returns the smallest error this instance can tolerate depending
* on its precision and value. * on its precision and value.

View File

@@ -152,14 +152,6 @@ public class PreciseNumber extends NumberInterface {
return new PreciseNumber(value.negate()); return new PreciseNumber(value.negate());
} }
@Override
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) {
return this;
}
return null;
}
@Override @Override
public String toString() { public String toString() {
return value.round(outputContext).toString(); return value.round(outputContext).toString();

View File

@@ -50,13 +50,13 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
for (String operator : manager.getAllOperators()) { for (String operator : manager.getAllOperators()) {
lexer.register(Pattern.sanitize(operator), TokenType.OP); lexer.register(Pattern.sanitize(operator), TokenType.OP);
} }
for (String operator : manager.getAllTreeValueOperators()){ for (String operator : manager.getAllTreeValueOperators()) {
lexer.register(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP); lexer.register(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
} }
for (String function : manager.getAllFunctions()) { for (String function : manager.getAllFunctions()) {
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION); lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
} }
for (String function : manager.getAllTreeValueFunctions()){ for (String function : manager.getAllTreeValueFunctions()) {
lexer.register(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION); lexer.register(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
} }
} }
@@ -66,13 +66,13 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
for (String operator : manager.getAllOperators()) { for (String operator : manager.getAllOperators()) {
lexer.unregister(Pattern.sanitize(operator), TokenType.OP); lexer.unregister(Pattern.sanitize(operator), TokenType.OP);
} }
for (String operator : manager.getAllTreeValueOperators()){ for (String operator : manager.getAllTreeValueOperators()) {
lexer.unregister(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP); lexer.unregister(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
} }
for (String function : manager.getAllFunctions()) { for (String function : manager.getAllFunctions()) {
lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION); lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION);
} }
for (String function : manager.getAllTreeValueFunctions()){ for (String function : manager.getAllTreeValueFunctions()) {
lexer.unregister(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION); lexer.unregister(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
} }
} }

View File

@@ -134,7 +134,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
TreeNode right = constructRecursive(matches); TreeNode right = constructRecursive(matches);
TreeNode left = constructRecursive(matches); TreeNode left = constructRecursive(matches);
if (left == null || right == null) return null; if (left == null || right == null) return null;
if(matchType == TokenType.OP) { if (matchType == TokenType.OP) {
return new NumberBinaryNode(operator, left, right); return new NumberBinaryNode(operator, left, right);
} else { } else {
return new TreeValueBinaryNode(operator, left, right); return new TreeValueBinaryNode(operator, left, right);
@@ -142,7 +142,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} else { } else {
TreeNode applyTo = constructRecursive(matches); TreeNode applyTo = constructRecursive(matches);
if (applyTo == null) return null; if (applyTo == null) return null;
if(matchType == TokenType.OP){ if (matchType == TokenType.OP) {
return new NumberUnaryNode(operator, applyTo); return new NumberUnaryNode(operator, applyTo);
} else { } else {
return new TreeValueUnaryNode(operator, applyTo); return new TreeValueUnaryNode(operator, applyTo);
@@ -155,7 +155,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) { } else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
String functionName = match.getContent(); String functionName = match.getContent();
CallNode node; CallNode node;
if(matchType == TokenType.FUNCTION){ if (matchType == TokenType.FUNCTION) {
node = new FunctionNode(functionName); node = new FunctionNode(functionName);
} else { } else {
node = new TreeValueFunctionNode(functionName); node = new TreeValueFunctionNode(functionName);

View File

@@ -14,7 +14,7 @@ public abstract class NumberImplementation {
/** /**
* The list of paths through which this implementation can be promoted. * The list of paths through which this implementation can be promoted.
*/ */
private Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> promotionPaths; private Map<String, Function<NumberInterface, NumberInterface>> promotionPaths;
/** /**
* The implementation class for this implementation. * The implementation class for this implementation.
*/ */
@@ -41,7 +41,7 @@ public abstract class NumberImplementation {
* *
* @return the map of documentation paths. * @return the map of documentation paths.
*/ */
public final Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> getPromotionPaths() { public final Map<String, Function<NumberInterface, NumberInterface>> getPromotionPaths() {
return promotionPaths; return promotionPaths;
} }

View File

@@ -2,6 +2,7 @@ package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.*; import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.variables.VariableDatabase;
/** /**
* A plugin class that can be externally implemented and loaded via the * A plugin class that can be externally implemented and loaded via the
@@ -219,4 +220,12 @@ public abstract class Plugin {
*/ */
public abstract void onDisable(); public abstract void onDisable();
/**
* Get the variable database.
* @return the variable database.
*/
public final VariableDatabase getVariableDatabase(){
return manager.getVariableDatabase();
}
} }

View File

@@ -3,6 +3,7 @@ package org.nwapw.abacus.plugin;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.*; import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.variables.VariableDatabase;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.HashMap; import java.util.HashMap;
@@ -49,10 +50,13 @@ public class PluginManager {
*/ */
private Set<Documentation> registeredDocumentation; private Set<Documentation> registeredDocumentation;
/** /**
* The list of number implementations that have been * The list of number implementation names.
* found by their implementation class.
*/ */
private Map<Class<? extends NumberInterface>, NumberImplementation> cachedInterfaceImplementations; private Map<Class<? extends NumberInterface>, String> interfaceImplementationNames;
/**
* The list of number implementations.
*/
private Map<Class<? extends NumberInterface>, NumberImplementation> interfaceImplementations;
/** /**
* The pi values for each implementation class that have already been computer. * The pi values for each implementation class that have already been computer.
*/ */
@@ -82,7 +86,8 @@ public class PluginManager {
registeredTreeValueOperators = new HashMap<>(); registeredTreeValueOperators = new HashMap<>();
registeredNumberImplementations = new HashMap<>(); registeredNumberImplementations = new HashMap<>();
registeredDocumentation = new HashSet<>(); registeredDocumentation = new HashSet<>();
cachedInterfaceImplementations = new HashMap<>(); interfaceImplementations = new HashMap<>();
interfaceImplementationNames = new HashMap<>();
cachedPi = new HashMap<>(); cachedPi = new HashMap<>();
listeners = new HashSet<>(); listeners = new HashSet<>();
} }
@@ -135,6 +140,8 @@ public class PluginManager {
*/ */
public void registerNumberImplementation(String name, NumberImplementation implementation) { public void registerNumberImplementation(String name, NumberImplementation implementation) {
registeredNumberImplementations.put(name, implementation); registeredNumberImplementations.put(name, implementation);
interfaceImplementationNames.put(implementation.getImplementation(), name);
interfaceImplementations.put(implementation.getImplementation(), implementation);
} }
/** /**
@@ -226,17 +233,17 @@ public class PluginManager {
* @return the implementation. * @return the implementation.
*/ */
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) { public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name); return interfaceImplementations.get(name);
NumberImplementation toReturn = null; }
for (String key : registeredNumberImplementations.keySet()) {
NumberImplementation implementation = registeredNumberImplementations.get(key); /**
if (implementation.getImplementation() == name) { * Gets the number implementation name for the given implementation class.
toReturn = implementation; *
break; * @param name the class for which to find the implementation name.
} * @return the implementation name.
} */
cachedInterfaceImplementations.put(name, toReturn); public String interfaceImplementationNameFor(Class<? extends NumberInterface> name) {
return toReturn; return interfaceImplementationNames.get(name);
} }
/** /**
@@ -329,7 +336,8 @@ public class PluginManager {
registeredTreeValueOperators.clear(); registeredTreeValueOperators.clear();
registeredNumberImplementations.clear(); registeredNumberImplementations.clear();
registeredDocumentation.clear(); registeredDocumentation.clear();
cachedInterfaceImplementations.clear(); interfaceImplementationNames.clear();
interfaceImplementations.clear();
cachedPi.clear(); cachedPi.clear();
listeners.forEach(e -> e.onUnload(this)); listeners.forEach(e -> e.onUnload(this));
} }
@@ -414,4 +422,12 @@ public class PluginManager {
public Set<Class<?>> getLoadedPluginClasses() { public Set<Class<?>> getLoadedPluginClasses() {
return loadedPluginClasses; return loadedPluginClasses;
} }
/**
* Gets the variable database.
* @return the database.
*/
public VariableDatabase getVariableDatabase(){
return abacus.getVariableDatabase();
}
} }

View File

@@ -1,13 +1,17 @@
package org.nwapw.abacus.plugin; package org.nwapw.abacus.plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nwapw.abacus.function.*; import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber; import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber; import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.tree.Reducer;
import org.nwapw.abacus.tree.TreeNode;
import org.nwapw.abacus.tree.VariableNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.function.BiFunction;
/** /**
* The plugin providing standard functions such as addition and subtraction to * The plugin providing standard functions such as addition and subtraction to
@@ -15,17 +19,51 @@ import java.util.function.BiFunction;
*/ */
public class StandardPlugin extends Plugin { public class StandardPlugin extends Plugin {
/**
* The set operator.
*/
public final TreeValueOperator opSet = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Override
public NumberInterface applyWithReducerInternal(NumberImplementation implementation, Reducer<? extends NumberInterface> reducer, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
NumberInterface value = params[1].reduce(reducer);
getVariableDatabase().getVariables().put(assignTo, value);
return value;
}
};
/**
* The define operator.
*/
public final TreeValueOperator opDefine = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Nullable
@Override
public NumberInterface applyWithReducerInternal(NumberImplementation implementation, Reducer<? extends NumberInterface> reducer, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
getVariableDatabase().getDefinitions().put(assignTo, params[1]);
return params[1].reduce(reducer);
}
};
/** /**
* The addition operator, + * The addition operator, +
*/ */
public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2; return params.length == 2;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].add(params[1]); return params[0].add(params[1]);
} }
}; };
@@ -34,12 +72,12 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2; return params.length == 2;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].subtract(params[1]); return params[0].subtract(params[1]);
} }
@@ -49,12 +87,12 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) { public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].negate(); return params[0].negate();
} }
}; };
@@ -63,12 +101,12 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) { public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2; return params.length == 2;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].multiply(params[1]); return params[0].multiply(params[1]);
} }
}; };
@@ -86,20 +124,6 @@ public class StandardPlugin extends Plugin {
return new NaiveNumber(Math.PI); return new NaiveNumber(Math.PI);
} }
}; };
/**
* The square root function.
*/
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return OP_CARET.apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass())));
}
};
/** /**
* The implementation for the infinite-precision BigDecimal. * The implementation for the infinite-precision BigDecimal.
*/ */
@@ -111,7 +135,7 @@ public class StandardPlugin extends Plugin {
@Override @Override
public NumberInterface instanceForPi() { public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880")); NumberInterface C = FUNCTION_SQRT.apply(this, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE; NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409"); NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M; NumberInterface X = M;
@@ -136,21 +160,17 @@ public class StandardPlugin extends Plugin {
return C.divide(sum); return C.divide(sum);
} }
}; };
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/** /**
* The division operator, / * The division operator, /
*/ */
public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) { public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(fromInt(params[0].getClass(), 0)) != 0; return params.length == 2 && params[1].compareTo(implementation.instanceForString(Integer.toString(0))) != 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].divide(params[1]); return params[0].divide(params[1]);
} }
}; };
@@ -160,18 +180,18 @@ public class StandardPlugin extends Plugin {
public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) { public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>(); //private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 return params.length == 1
&& params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0 && params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
&& params[0].signum() >= 0; && params[0].signum() >= 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
if (params[0].signum() == 0) { if (params[0].signum() == 0) {
return fromInt(params[0].getClass(), 1); return implementation.instanceForString("1");
} }
NumberInterface one = fromInt(params[0].getClass(), 1); NumberInterface one = implementation.instanceForString("1");
NumberInterface factorial = params[0]; NumberInterface factorial = params[0];
NumberInterface multiplier = params[0]; NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers. //It is necessary to later prevent calls of factorial on anything but non-negative integers.
@@ -191,27 +211,27 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0 return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0; && params[1].fractionalPart().signum() == 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
if (params[0].compareTo(params[1]) < 0 || if (params[0].compareTo(params[1]) < 0 ||
params[0].signum() < 0 || params[0].signum() < 0 ||
(params[0].signum() == 0 && params[1].signum() != 0)) return fromInt(params[0].getClass(), 0); (params[0].signum() == 0 && params[1].signum() != 0)) return implementation.instanceForString("0");
NumberInterface total = fromInt(params[0].getClass(), 1); NumberInterface total = implementation.instanceForString("1");
NumberInterface multiplyBy = params[0]; NumberInterface multiplyBy = params[0];
NumberInterface remainingMultiplications = params[1]; NumberInterface remainingMultiplications = params[1];
NumberInterface halfway = params[0].divide(fromInt(params[0].getClass(), 2)); NumberInterface halfway = params[0].divide(implementation.instanceForString("2"));
if (remainingMultiplications.compareTo(halfway) > 0) { if (remainingMultiplications.compareTo(halfway) > 0) {
remainingMultiplications = params[0].subtract(remainingMultiplications); remainingMultiplications = params[0].subtract(remainingMultiplications);
} }
while (remainingMultiplications.signum() > 0) { while (remainingMultiplications.signum() > 0) {
total = total.multiply(multiplyBy); total = total.multiply(multiplyBy);
remainingMultiplications = remainingMultiplications.subtract(fromInt(params[0].getClass(), 1)); remainingMultiplications = remainingMultiplications.subtract(implementation.instanceForString("1"));
multiplyBy = multiplyBy.subtract(fromInt(params[0].getClass(), 1)); multiplyBy = multiplyBy.subtract(implementation.instanceForString("1"));
} }
return total; return total;
} }
@@ -221,14 +241,14 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0 return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0; && params[1].fractionalPart().signum() == 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return OP_NPR.apply(params).divide(OP_FACTORIAL.apply(params[1])); return OP_NPR.apply(implementation, params).divide(OP_FACTORIAL.apply(implementation, params[1]));
} }
}; };
/** /**
@@ -236,13 +256,13 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberFunction FUNCTION_ABS = new NumberFunction() { public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].multiply(fromInt(params[0].getClass(), params[0].signum())); return params[0].multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
} }
}; };
/** /**
@@ -250,25 +270,25 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberFunction FUNCTION_LN = new NumberFunction() { public static final NumberFunction FUNCTION_LN = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(fromInt(params[0].getClass(), 0)) > 0; return params.length == 1 && params[0].compareTo(implementation.instanceForString("0")) > 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface param = params[0]; NumberInterface param = params[0];
NumberInterface one = fromInt(param.getClass(), 1); NumberInterface one = implementation.instanceForString("1");
int powersOf2 = 0; int powersOf2 = 0;
while (FUNCTION_ABS.apply(param.subtract(one)).compareTo(new NaiveNumber(0.1).promoteTo(param.getClass())) >= 0) { while (FUNCTION_ABS.apply(implementation, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
if (param.subtract(one).signum() == 1) { if (param.subtract(one).signum() == 1) {
param = param.divide(fromInt(param.getClass(), 2)); param = param.divide(implementation.instanceForString("2"));
powersOf2++; powersOf2++;
if (param.subtract(one).signum() != 1) { if (param.subtract(one).signum() != 1) {
break; break;
//No infinite loop for you. //No infinite loop for you.
} }
} else { } else {
param = param.multiply(fromInt(param.getClass(), 2)); param = param.multiply(implementation.instanceForString("2"));
powersOf2--; powersOf2--;
if (param.subtract(one).signum() != -1) { if (param.subtract(one).signum() != -1) {
break; break;
@@ -276,7 +296,7 @@ public class StandardPlugin extends Plugin {
} }
} }
} }
return getLog2(param).multiply(fromInt(param.getClass(), powersOf2)).add(getLogPartialSum(param)); return getLog2(implementation, param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(implementation, param));
} }
/** /**
@@ -285,16 +305,16 @@ public class StandardPlugin extends Plugin {
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.) * @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum. * @return the partial sum.
*/ */
private NumberInterface getLogPartialSum(NumberInterface x) { private NumberInterface getLogPartialSum(NumberImplementation implementation, NumberInterface x) {
NumberInterface maxError = x.getMaxError(); NumberInterface maxError = x.getMaxError();
x = x.subtract(fromInt(x.getClass(), 1)); //Terms used are for log(x+1). x = x.subtract(implementation.instanceForString("1")); //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;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) { while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
n++; n++;
currentNumerator = currentNumerator.multiply(x).negate(); currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(fromInt(x.getClass(), n)); currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(n)));
sum = sum.add(currentTerm); sum = sum.add(currentTerm);
} }
return sum; return sum;
@@ -305,21 +325,21 @@ public class StandardPlugin extends Plugin {
* @param number a number of the same type as the return type. (Used for precision.) * @param number a number of the same type as the return type. (Used for precision.)
* @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(NumberImplementation implementation, NumberInterface number) {
NumberInterface maxError = number.getMaxError(); NumberInterface maxError = number.getMaxError();
//NumberInterface errorBound = fromInt(number.getClass(), 1); //NumberInterface errorBound = implementation.instanceForString("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.
//a is also an error bound. //a is also an error bound.
NumberInterface a = fromInt(number.getClass(), 1), b = a, c = a; NumberInterface a = implementation.instanceForString("1"), b = a, c = a;
NumberInterface sum = fromInt(number.getClass(), 0); NumberInterface sum = implementation.instanceForString("0");
NumberInterface one = fromInt(number.getClass(), 1); NumberInterface one = implementation.instanceForString("1");
int n = 0; int n = 0;
while (a.compareTo(maxError) >= 1) { while (a.compareTo(maxError) >= 1) {
n++; n++;
a = a.divide(fromInt(number.getClass(), 3)); a = a.divide(implementation.instanceForString("3"));
b = b.divide(fromInt(number.getClass(), 4)); b = b.divide(implementation.instanceForString("4"));
c = one.divide(fromInt(number.getClass(), n)); c = one.divide(implementation.instanceForString(Integer.toString(n)));
sum = sum.add(a.add(b).multiply(c)); sum = sum.add(a.add(b).multiply(c));
} }
return sum; return sum;
@@ -330,44 +350,88 @@ public class StandardPlugin extends Plugin {
*/ */
public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() { public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return fromInt(params[0].getClass(), (int) Math.round(Math.random() * params[0].floor().intValue())); return implementation.instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
} }
}; };
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>(); /**
* The caret / pow operator, ^
*/
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface zero = implementation.instanceForString("0");
return params.length == 2
&& !(params[0].compareTo(zero) == 0
&& params[1].compareTo(zero) == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface zero = implementation.instanceForString("0");
if (params[0].compareTo(zero) == 0)
return zero;
else if (params[1].compareTo(zero) == 0)
return implementation.instanceForString("1");
//Detect integer bases:
if (params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
&& FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))) < 0
&& FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString("1")) >= 0) {
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(implementation, newParams));
}
return FUNCTION_EXP.apply(implementation, FUNCTION_LN.apply(implementation, FUNCTION_ABS.apply(implementation, params[0])).multiply(params[1]));
}
};
/**
* The square root function.
*/
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return OP_CARET.apply(implementation, params[0], implementation.instanceForString(".5"));
}
};
private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/** /**
* The exponential function, exp(1) = e^1 = 2.71... * The exponential function, exp(1) = e^1 = 2.71...
*/ */
public static final NumberFunction FUNCTION_EXP = new NumberFunction() { public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface maxError = params[0].getMaxError(); NumberInterface maxError = params[0].getMaxError();
int n = 0; int n = 0;
if (params[0].signum() < 0) { if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()}; NumberInterface[] negatedParams = {params[0].negate()};
return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams)); return implementation.instanceForString("1").divide(applyInternal(implementation, negatedParams));
} 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.
NumberInterface sum = fromInt(params[0].getClass(), 1); NumberInterface sum = implementation.instanceForString("1");
NumberInterface nextNumerator = params[0]; NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError; NumberInterface left = params[0].multiply(implementation.instanceForString("3").intPow(params[0].ceiling().intValue())), right = maxError;
do { do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1))); sum = sum.add(nextNumerator.divide(factorial(implementation, n + 1)));
n++; n++;
nextNumerator = nextNumerator.multiply(params[0]); nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]); left = left.multiply(params[0]);
NumberInterface nextN = fromInt(params[0].getClass(), n + 1); NumberInterface nextN = implementation.instanceForString(Integer.toString(n + 1));
right = right.multiply(nextN); right = right.multiply(nextN);
//System.out.println(left + ", " + right); //System.out.println(left + ", " + right);
} }
@@ -377,58 +441,28 @@ public class StandardPlugin extends Plugin {
} }
} }
}; };
/**
* The caret / pow operator, ^
*/
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
@Override
public boolean matchesParams(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
return params.length == 2
&& !(params[0].compareTo(zero) == 0
&& params[1].compareTo(zero) == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
if (params[0].compareTo(zero) == 0)
return zero;
else if (params[1].compareTo(zero) == 0)
return fromInt(params[0].getClass(), 1);
//Detect integer bases:
if (params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 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) {
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams));
}
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
};
/** /**
* The sine function (the argument is interpreted in radians). * The sine function (the argument is interpreted in radians).
*/ */
public final NumberFunction functionSin = new NumberFunction() { public final NumberFunction functionSin = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface pi = piFor(params[0].getClass()); NumberInterface pi = piFor(params[0].getClass());
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2)); NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
NumberInterface theta = getSmallAngle(params[0], pi); NumberInterface theta = getSmallAngle(implementation, params[0], pi);
//System.out.println(theta); //System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) { if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
theta = theta.subtract(twoPi); theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(fromInt(pi.getClass(), 2))) > 0) { } else if (theta.compareTo(pi.divide(implementation.instanceForString("2"))) > 0) {
theta = pi.subtract(theta); theta = pi.subtract(theta);
} }
//System.out.println(theta); //System.out.println(theta);
return sinTaylor(theta); return sinTaylor(implementation, theta);
} }
}; };
/** /**
@@ -436,13 +470,13 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionCos = new NumberFunction() { public final NumberFunction functionCos = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return functionSin.apply(piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) return functionSin.apply(implementation, piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(params[0])); .subtract(params[0]));
} }
}; };
@@ -451,13 +485,13 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionTan = new NumberFunction() { public final NumberFunction functionTan = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0])); return functionSin.apply(implementation, params[0]).divide(functionCos.apply(implementation, params[0]));
} }
}; };
/** /**
@@ -465,13 +499,13 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionSec = new NumberFunction() { public final NumberFunction functionSec = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionCos.apply(params[0])); return implementation.instanceForString("1").divide(functionCos.apply(implementation, params[0]));
} }
}; };
/** /**
@@ -479,13 +513,13 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionCsc = new NumberFunction() { public final NumberFunction functionCsc = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionSin.apply(params[0])); return implementation.instanceForString("1").divide(functionSin.apply(implementation, params[0]));
} }
}; };
/** /**
@@ -493,13 +527,13 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionCot = new NumberFunction() { public final NumberFunction functionCot = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0])); return functionCos.apply(implementation, params[0]).divide(functionSin.apply(implementation, params[0]));
} }
}; };
@@ -508,28 +542,28 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionArcsin = new NumberFunction() { public final NumberFunction functionArcsin = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 return params.length == 1
&& FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0; && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
if (FUNCTION_ABS.apply(params[0]).compareTo(new NaiveNumber(0.8).promoteTo(params[0].getClass())) >= 0) { if (FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
NumberInterface[] newParams = {FUNCTION_SQRT.apply(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))}; NumberInterface[] newParams = {FUNCTION_SQRT.apply(implementation, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(applyInternal(newParams)).multiply(fromInt(params[0].getClass(), params[0].signum())); .subtract(applyInternal(implementation, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
} }
NumberInterface currentTerm = params[0], sum = currentTerm, NumberInterface currentTerm = params[0], sum = currentTerm,
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(fromInt(sum.getClass(), 1).subtract(multiplier)), multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
power = currentTerm, coefficient = fromInt(params[0].getClass(), 1); power = currentTerm, coefficient = implementation.instanceForString("1");
int exponent = 1; int exponent = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(summandBound) > 0) { while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(summandBound) > 0) {
exponent += 2; exponent += 2;
power = power.multiply(multiplier); power = power.multiply(multiplier);
coefficient = coefficient.multiply(fromInt(params[0].getClass(), exponent - 2)) coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
.divide(fromInt(params[0].getClass(), exponent - 1)); .divide(implementation.instanceForString(Integer.toString(exponent - 1)));
currentTerm = power.multiply(coefficient).divide(fromInt(power.getClass(), exponent)); currentTerm = power.multiply(coefficient).divide(implementation.instanceForString(Integer.toString(exponent)));
sum = sum.add(currentTerm); sum = sum.add(currentTerm);
} }
return sum; return sum;
@@ -541,14 +575,14 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionArccos = new NumberFunction() { public final NumberFunction functionArccos = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0; return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(functionArcsin.apply(params)); .subtract(functionArcsin.apply(implementation, params));
} }
}; };
@@ -557,14 +591,14 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionArccsc = new NumberFunction() { public final NumberFunction functionArccsc = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0; return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])}; NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
return functionArcsin.apply(reciprocalParamArr); return functionArcsin.apply(implementation, reciprocalParamArr);
} }
}; };
@@ -573,14 +607,14 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionArcsec = new NumberFunction() { public final NumberFunction functionArcsec = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0; return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])}; NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
return functionArccos.apply(reciprocalParamArr); return functionArccos.apply(implementation, reciprocalParamArr);
} }
}; };
@@ -589,36 +623,36 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionArctan = new NumberFunction() { public final NumberFunction functionArctan = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
if (params[0].signum() == -1) { if (params[0].signum() == -1) {
NumberInterface[] negatedParams = {params[0].negate()}; NumberInterface[] negatedParams = {params[0].negate()};
return applyInternal(negatedParams).negate(); return applyInternal(implementation, negatedParams).negate();
} }
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) > 0) { if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
NumberInterface[] reciprocalParams = {fromInt(params[0].getClass(), 1).divide(params[0])}; NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(applyInternal(reciprocalParams)); .subtract(applyInternal(implementation, reciprocalParams));
} }
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) == 0) { if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 4)); return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
} }
if (params[0].compareTo(new NaiveNumber(0.9).promoteTo(params[0].getClass())) >= 0) { if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
NumberInterface[] newParams = {params[0].multiply(fromInt(params[0].getClass(), 2)) NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
.divide(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))}; .divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
return applyInternal(newParams).divide(fromInt(params[0].getClass(), 2)); return applyInternal(implementation, newParams).divide(implementation.instanceForString("2"));
} }
NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm, NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate(); maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
int n = 1; int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) { while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
n += 2; n += 2;
currentPower = currentPower.multiply(multiplier); currentPower = currentPower.multiply(multiplier);
currentTerm = currentPower.divide(fromInt(currentPower.getClass(), n)); currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));
sum = sum.add(currentTerm); sum = sum.add(currentTerm);
} }
return sum; return sum;
@@ -630,14 +664,14 @@ public class StandardPlugin extends Plugin {
*/ */
public final NumberFunction functionArccot = new NumberFunction() { public final NumberFunction functionArccot = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1; return params.length == 1;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(functionArctan.apply(params)); .subtract(functionArctan.apply(implementation, params));
} }
}; };
@@ -645,40 +679,25 @@ public class StandardPlugin extends Plugin {
super(manager); super(manager);
} }
/**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
*
* @param x the value at which the series is evaluated.
* @param nthTermFunction the function that returns the nth term of the series, in the format term(x, n).
* @param n the number of terms in the partial sum.
* @return the value of the partial sum that has the same class as x.
*/
private static NumberInterface sumSeries(NumberInterface x, BiFunction<Integer, NumberInterface, NumberInterface> nthTermFunction, int n) {
NumberInterface sum = fromInt(x.getClass(), 0);
for (int i = 0; i <= n; i++) {
sum = sum.add(nthTermFunction.apply(i, x));
}
return sum;
}
/** /**
* 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.
* *
* @param numberClass type of number to return. * @param implementation type of number to return.
* @param n non-negative integer. * @param n non-negative integer.
* @return a number of numClass with value n factorial. * @return a number of numClass with value n factorial.
*/ */
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n) { public static NumberInterface factorial(NumberImplementation implementation, int n) {
if (!FACTORIAL_LISTS.containsKey(numberClass)) {
FACTORIAL_LISTS.put(numberClass, new ArrayList<>()); if (!FACTORIAL_LISTS.containsKey(implementation)) {
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1)); FACTORIAL_LISTS.put(implementation, new ArrayList<>());
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1)); FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
} }
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass); ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(implementation);
if (n >= list.size()) { if (n >= list.size()) {
while (list.size() < n + 16) { while (list.size() < n + 16) {
list.add(list.get(list.size() - 1).multiply(fromInt(numberClass, list.size()))); list.add(list.get(list.size() - 1).multiply(implementation.instanceForString(Integer.toString(list.size()))));
} }
} }
return list.get(n); return list.get(n);
@@ -690,16 +709,16 @@ public class StandardPlugin extends Plugin {
* @param x where the series is evaluated. * @param x where the series is evaluated.
* @return the value of the series * @return the value of the series
*/ */
private static NumberInterface sinTaylor(NumberInterface x) { private static NumberInterface sinTaylor(NumberImplementation implementation, 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 = x.getMaxError(); NumberInterface maxError = x.getMaxError();
int n = 1; int n = 1;
do { do {
n += 2; n += 2;
power = power.multiply(multiplier); power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n)); currentTerm = power.divide(factorial(implementation, n));
sum = sum.add(currentTerm); sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0); } while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0);
return sum; return sum;
} }
@@ -709,33 +728,16 @@ public class StandardPlugin extends Plugin {
* @param phi an angle (in radians). * @param phi an angle (in radians).
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi. * @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/ */
private static NumberInterface getSmallAngle(NumberInterface phi, NumberInterface pi) { private static NumberInterface getSmallAngle(NumberImplementation implementation, NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2)); NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi NumberInterface theta = FUNCTION_ABS.apply(implementation, phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi). .multiply(FUNCTION_ABS.apply(implementation, phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) { if (phi.signum() < 0) {
theta = twoPi.subtract(theta); theta = twoPi.subtract(theta);
} }
return theta; return theta;
} }
/**
* Returns a number of class numType with value n.
*
* @param numType class of number to return.
* @param n value of returned number.
* @return numClass instance with value n.
*/
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n) {
if (!integerValues.containsKey(numType)) {
integerValues.put(numType, new HashMap<>());
}
if (!integerValues.get(numType).containsKey(n)) {
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
}
@Override @Override
public void onEnable() { public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE); registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
@@ -749,6 +751,9 @@ public class StandardPlugin extends Plugin {
registerOperator("^", OP_CARET); registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL); registerOperator("!", OP_FACTORIAL);
registerTreeValueOperator("=", opSet);
registerTreeValueOperator(":=", opDefine);
registerOperator("nPr", OP_NPR); registerOperator("nPr", OP_NPR);
registerOperator("nCr", OP_NCR); registerOperator("nCr", OP_NCR);
@@ -824,6 +829,6 @@ public class StandardPlugin extends Plugin {
@Override @Override
public void onDisable() { public void onDisable() {
FACTORIAL_LISTS.clear();
} }
} }

View File

@@ -1,8 +1,14 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.*; import org.nwapw.abacus.function.NumberFunction;
import org.nwapw.abacus.function.NumberOperator;
import org.nwapw.abacus.function.TreeValueFunction;
import org.nwapw.abacus.function.TreeValueOperator;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PromotionManager;
import org.nwapw.abacus.number.PromotionResult;
import org.nwapw.abacus.variables.VariableDatabase;
/** /**
* A reducer implementation that turns a tree into a single number. * A reducer implementation that turns a tree into a single number.
@@ -26,19 +32,28 @@ public class NumberReducer implements Reducer<NumberInterface> {
@Override @Override
public NumberInterface reduceNode(TreeNode node, Object... children) { public NumberInterface reduceNode(TreeNode node, Object... children) {
PromotionManager manager = abacus.getPromotionManager();
if (node instanceof NumberNode) { if (node instanceof NumberNode) {
return abacus.numberFromString(((NumberNode) node).getNumber()); return abacus.getNumberImplementation().instanceForString(((NumberNode) node).getNumber());
} else if(node instanceof VariableNode) { } else if (node instanceof VariableNode) {
return abacus.numberFromString("0"); VariableDatabase database = abacus.getVariableDatabase();
String name = ((VariableNode) node).getVariable();
NumberInterface variable = database.getVariables().get(name);
if(variable != null) return variable;
TreeNode definition = database.getDefinitions().get(name);
if(definition != null) return definition.reduce(this);
return abacus.getNumberImplementation().instanceForString("0");
} else if (node instanceof NumberBinaryNode) { } else if (node instanceof NumberBinaryNode) {
NumberInterface left = (NumberInterface) children[0]; NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1]; NumberInterface right = (NumberInterface) children[1];
NumberOperator operator = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()); NumberOperator operator = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation());
return operator.apply(left, right); PromotionResult result = manager.promote(left, right);
if (result == null) return null;
return operator.apply(result.getPromotedTo(), result.getItems());
} else if (node instanceof NumberUnaryNode) { } else if (node instanceof NumberUnaryNode) {
NumberInterface child = (NumberInterface) children[0]; NumberInterface child = (NumberInterface) children[0];
NumberOperator operator = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()); NumberOperator operator = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation());
return operator.apply(child); return operator.apply(abacus.getPluginManager().interfaceImplementationFor(child.getClass()), child);
} else if (node instanceof FunctionNode) { } else if (node instanceof FunctionNode) {
NumberInterface[] convertedChildren = new NumberInterface[children.length]; NumberInterface[] convertedChildren = new NumberInterface[children.length];
for (int i = 0; i < convertedChildren.length; i++) { for (int i = 0; i < convertedChildren.length; i++) {
@@ -46,29 +61,31 @@ public class NumberReducer implements Reducer<NumberInterface> {
} }
NumberFunction function = abacus.getPluginManager().functionFor(((FunctionNode) node).getCallTo()); NumberFunction function = abacus.getPluginManager().functionFor(((FunctionNode) node).getCallTo());
if (function == null) return null; if (function == null) return null;
return function.apply(convertedChildren); PromotionResult result = manager.promote(convertedChildren);
} else if (node instanceof TreeValueFunctionNode){ if (result == null) return null;
return function.apply(result.getPromotedTo(), result.getItems());
} else if (node instanceof TreeValueFunctionNode) {
CallNode callNode = (CallNode) node; CallNode callNode = (CallNode) node;
TreeNode[] realChildren = new TreeNode[callNode.getChildren().size()]; TreeNode[] realChildren = new TreeNode[callNode.getChildren().size()];
for(int i = 0; i < realChildren.length; i++){ for (int i = 0; i < realChildren.length; i++) {
realChildren[i] = callNode.getChildren().get(i); realChildren[i] = callNode.getChildren().get(i);
} }
TreeValueFunction function = TreeValueFunction function =
abacus.getPluginManager().treeValueFunctionFor(callNode.getCallTo()); abacus.getPluginManager().treeValueFunctionFor(callNode.getCallTo());
if(function == null) return null; if (function == null) return null;
return function.applyWithReducer(this, realChildren); return function.applyWithReducer(abacus.getNumberImplementation(), this, realChildren);
} else if (node instanceof TreeValueBinaryNode) { } else if (node instanceof TreeValueBinaryNode) {
BinaryNode binaryNode = (BinaryNode) node; BinaryNode binaryNode = (BinaryNode) node;
TreeValueOperator operator = abacus.getPluginManager() TreeValueOperator operator = abacus.getPluginManager()
.treeValueOperatorFor(binaryNode.getOperation()); .treeValueOperatorFor(binaryNode.getOperation());
if(operator == null) return null; if (operator == null) return null;
return operator.applyWithReducer(this, binaryNode.getLeft(), binaryNode.getRight()); return operator.applyWithReducer(abacus.getNumberImplementation(), this, binaryNode.getLeft(), binaryNode.getRight());
} else if(node instanceof TreeValueUnaryNode) { } else if (node instanceof TreeValueUnaryNode) {
UnaryNode unaryNode = (UnaryNode) node; UnaryNode unaryNode = (UnaryNode) node;
TreeValueOperator operator = abacus.getPluginManager() TreeValueOperator operator = abacus.getPluginManager()
.treeValueOperatorFor(unaryNode.getOperation()); .treeValueOperatorFor(unaryNode.getOperation());
if(operator == null) return null; if (operator == null) return null;
return operator.applyWithReducer(this, unaryNode.getApplyTo()); return operator.applyWithReducer(abacus.getNumberImplementation(), this, unaryNode.getApplyTo());
} }
return null; return null;
} }

View File

@@ -0,0 +1,90 @@
package org.nwapw.abacus
import org.nwapw.abacus.config.Configuration
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.number.PromotionManager
import org.nwapw.abacus.parsing.LexerTokenizer
import org.nwapw.abacus.parsing.ShuntingYardParser
import org.nwapw.abacus.parsing.TreeBuilder
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.plugin.PluginManager
import org.nwapw.abacus.plugin.StandardPlugin
import org.nwapw.abacus.tree.NumberReducer
import org.nwapw.abacus.tree.TreeNode
import org.nwapw.abacus.variables.VariableDatabase
/**
* Core class to handle all mathematics.
*
* The main calculator class. This is responsible
* for piecing together all of the components, allowing
* their interaction with each other.
*
* @property configuration the configuration to use.
*/
class Abacus(val configuration: Configuration) {
/**
* The tokenizer used to convert strings into tokens.
*/
private val tokenizer = LexerTokenizer()
/**
* Parser the parser used to convert tokens into trees.
*/
private val parser = ShuntingYardParser()
/**
* The plugin manager used to handle loading and unloading plugins.
*/
val pluginManager = PluginManager(this)
/**
* The reducer used to turn trees into a single number.
*/
val numberReducer = NumberReducer(this)
/**
* The tree builder that handles the conversion of strings into trees.
*/
val treeBuilder = TreeBuilder(tokenizer, parser)
/**
* The promotion manager used to convert between number implementations.
*/
val promotionManager = PromotionManager(this)
/**
* The database of variable definitions.
*/
val variableDatabase = VariableDatabase(this)
/**
* The number implementation used by default.
*/
val numberImplementation: NumberImplementation
get() {
val selectedImplementation =
pluginManager.numberImplementationFor(configuration.numberImplementation)
if (selectedImplementation != null) return selectedImplementation
return StandardPlugin.IMPLEMENTATION_NAIVE
}
init {
pluginManager.addListener(tokenizer)
pluginManager.addListener(parser)
pluginManager.addListener(promotionManager)
pluginManager.addListener(variableDatabase)
}
/**
* Parses a string into a tree structure using the main
* tree builder.
*
* @param input the input string to parse
* @return the resulting tree, null if the tree builder or the produced tree are null.
*/
fun parseString(input: String): TreeNode? = treeBuilder.fromString(input)
/**
* Evaluates the given tree using the main
* number reducer.
*
* @param tree the tree to reduce, must not be null.
* @return the resulting number, or null of the reduction failed.
*/
fun evaluateTree(tree: TreeNode): NumberInterface? = tree.reduce(numberReducer)
}

View File

@@ -0,0 +1,20 @@
package org.nwapw.abacus.config
/**
* A class that holds information that tells Abacus how to behave.
*
* Configuration stores information about how Abacus should behave, for
* instance, what number implementation it should use and what
* plugins should be ignored during loading.
*
* @property numberImplementation the number implementation Abacus should use for loading.
* @param disabledPlugins the plugins that should be disabled and not loaded by the plugin manager.
*/
open class Configuration(var numberImplementation: String = "<default>", disabledPlugins: Array<String> = emptyArray()) {
/**
* The set of disabled plugins that should be ignored by the plugin manager.
*/
val disabledPlugins = disabledPlugins.toMutableSet()
}

View File

@@ -1,5 +1,8 @@
package org.nwapw.abacus.function.applicable package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.function.DomainException
import org.nwapw.abacus.plugin.NumberImplementation
/** /**
* A class that can be applied to arguments. * A class that can be applied to arguments.
* *
@@ -15,7 +18,7 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameter array to verify for compatibility. * @param params the parameter array to verify for compatibility.
* @return whether the array can be used with applyInternal. * @return whether the array can be used with applyInternal.
*/ */
fun matchesParams(params: Array<out T>): Boolean fun matchesParams(implementation: NumberImplementation, params: Array<out T>): Boolean
/** /**
* Applies the applicable object to the given parameters, * Applies the applicable object to the given parameters,
@@ -23,7 +26,7 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameters to apply to. * @param params the parameters to apply to.
* @return the result of the application. * @return the result of the application.
*/ */
fun applyInternal(params: Array<out T>): O? fun applyInternal(implementation: NumberImplementation, params: Array<out T>): O
/** /**
* If the parameters can be used with this applicable, returns * If the parameters can be used with this applicable, returns
@@ -32,9 +35,9 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameters to apply to. * @param params the parameters to apply to.
* @return the result of the operation, or null if parameters do not match. * @return the result of the operation, or null if parameters do not match.
*/ */
fun apply(vararg params: T): O? { fun apply(implementation: NumberImplementation, vararg params: T): O {
if (!matchesParams(params)) return null if (!matchesParams(implementation, params)) throw DomainException()
return applyInternal(params) return applyInternal(implementation, params)
} }
} }

View File

@@ -1,5 +1,7 @@
package org.nwapw.abacus.function.applicable package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.function.DomainException
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer import org.nwapw.abacus.tree.Reducer
/** /**
@@ -18,7 +20,7 @@ interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
* given parameters. * given parameters.
* @param params the parameters to check. * @param params the parameters to check.
*/ */
fun matchesParams(params: Array<out T>): Boolean fun matchesParams(implementation: NumberImplementation, params: Array<out T>): Boolean
/** /**
* Applies this applicable to the given arguments, and reducer. * Applies this applicable to the given arguments, and reducer.
@@ -26,7 +28,7 @@ interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
* @param params the arguments to apply to. * @param params the arguments to apply to.
* @return the result of the application. * @return the result of the application.
*/ */
fun applyWithReducerInternal(reducer: Reducer<R>, params: Array<out T>): O? fun applyWithReducerInternal(implementation: NumberImplementation, reducer: Reducer<R>, params: Array<out T>): O
/** /**
* Applies this applicable to the given arguments, and reducer, * Applies this applicable to the given arguments, and reducer,
@@ -35,9 +37,9 @@ interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
* @param params the arguments to apply to. * @param params the arguments to apply to.
* @return the result of the application, or null if the arguments are incompatible. * @return the result of the application, or null if the arguments are incompatible.
*/ */
fun applyWithReducer(reducer: Reducer<R>, vararg params: T): O? { fun applyWithReducer(implementation: NumberImplementation, reducer: Reducer<R>, vararg params: T): O {
if (!matchesParams(params)) return null if (!matchesParams(implementation, params)) throw DomainException()
return applyWithReducerInternal(reducer, params) return applyWithReducerInternal(implementation, reducer, params)
} }
} }

View File

@@ -0,0 +1,16 @@
@file:JvmName("NumberUtils")
package org.nwapw.abacus.number
typealias PromotionFunction = java.util.function.Function<NumberInterface, NumberInterface>
typealias PromotionPath = List<PromotionFunction>
typealias NumberClass = Class<NumberInterface>
/**
* Promote a number through this path. The functions in this path
* are applied in order to the number, and the final result is returned.
*
* @param from the number to start from.
*/
fun PromotionPath.promote(from: NumberInterface): NumberInterface {
return fold(from, { current, function -> function.apply(current) })
}

View File

@@ -0,0 +1,83 @@
package org.nwapw.abacus.number
import org.nwapw.abacus.Abacus
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.plugin.PluginListener
import org.nwapw.abacus.plugin.PluginManager
import java.util.function.Function
/**
* A class that handles promotions based on priority and the
* transition paths each implementation provides.
*
* @property abacus the Abacus instance to use to access other components.
*/
class PromotionManager(val abacus: Abacus) : PluginListener {
/**
* The already computed paths
*/
val computePaths = mutableMapOf<Pair<NumberImplementation, NumberImplementation>, PromotionPath?>()
/**
* Computes a path between a starting and an ending implementation.
*
* @param from the implementation to start from.
* @param to the implementation to get to.
* @return the resulting promotion path, or null if it is not found
*/
fun computePathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
val fromName = abacus.pluginManager.interfaceImplementationNameFor(from.implementation)
val toName = abacus.pluginManager.interfaceImplementationNameFor(to.implementation)
if(fromName == toName) return listOf(Function { it })
if(from.promotionPaths.containsKey(toName))
return listOf(from.promotionPaths[toName] ?: return null)
return null
}
/**
* If a path between the given implementations has already been computed, uses
* the already calculated path. Otherwise, calls [computePathBetween] to compute a new
* path.
*
* @param from the implementation to start from.
* @param to the implementation to get to.
* @return the resulting promotion path, or null if it is not found
*/
fun getPathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
return computePaths.computeIfAbsent(from to to, {
computePathBetween(it.first, it.second)
})
}
/**
* Promote all the numbers in the list to the same number implementation, to ensure
* they can be used with each other. Finds the highest priority implementation
* in the list, and promotes all other numbers to it.
*
* @param numbers the numbers to promote.
* @return the resulting promotion result.
*/
fun promote(vararg numbers: NumberInterface): PromotionResult? {
val pluginManager = abacus.pluginManager
val implementations = numbers.map { pluginManager.interfaceImplementationFor(it.javaClass) }
val highestPriority = implementations.sortedBy { it.priority }.last()
return PromotionResult(items = numbers.map {
if(it.javaClass == highestPriority.implementation) it
else getPathBetween(pluginManager.interfaceImplementationFor(it.javaClass), highestPriority)
?.promote(it) ?: return null
}.toTypedArray(), promotedTo = highestPriority)
}
override fun onLoad(manager: PluginManager?) {
}
override fun onUnload(manager: PluginManager?) {
computePaths.clear()
}
}

View File

@@ -0,0 +1,11 @@
package org.nwapw.abacus.number
import org.nwapw.abacus.plugin.NumberImplementation
/**
* The result of promoting an array of NumberInterfaces.
*
* @param promotedTo the implementation to which the numbers were promoted.
* @param items the items the items resulting from the promotion.
*/
data class PromotionResult(val promotedTo: NumberImplementation, val items: Array<NumberInterface>)

View File

@@ -0,0 +1,37 @@
package org.nwapw.abacus.variables
import org.nwapw.abacus.Abacus
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.PluginListener
import org.nwapw.abacus.plugin.PluginManager
import org.nwapw.abacus.tree.TreeNode
/**
* A database for variables and definition.
*
* The variable database is used to keep track of
* variables and definitions throughout the calculator.
*
* @property abacus the Abacus instance.
*/
class VariableDatabase(val abacus: Abacus): PluginListener {
/**
* The variables that are stored in the database.
*/
val variables = mutableMapOf<String, NumberInterface>()
/**
* The definitions that are stored in the database.
*/
val definitions = mutableMapOf<String, TreeNode>()
override fun onLoad(manager: PluginManager?) {
}
override fun onUnload(manager: PluginManager?) {
variables.clear()
definitions.clear()
}
}

View File

@@ -5,13 +5,14 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.DomainException;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
public class CalculationTests { public class CalculationTests {
private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{})); private static Abacus abacus = new Abacus(new Configuration( "precise", new String[]{}));
@BeforeClass @BeforeClass
public static void prepareTests() { public static void prepareTests() {
@@ -28,11 +29,14 @@ public class CalculationTests {
Assert.assertTrue(result.toString().startsWith(output)); Assert.assertTrue(result.toString().startsWith(output));
} }
private void testEvalError(String input, String parseOutput) { private void testDomainException(String input, String parseOutput) {
TreeNode parsedTree = abacus.parseString(input); TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree); Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput); Assert.assertEquals(parsedTree.toString(), parseOutput);
Assert.assertNull(abacus.evaluateTree(parsedTree)); try {
abacus.evaluateTree(parsedTree);
Assert.fail("Function did not throw DomainException.");
} catch (DomainException e){ }
} }
@Test @Test
@@ -73,7 +77,7 @@ public class CalculationTests {
@Test @Test
public void testLn() { public void testLn() {
testEvalError("ln(-1)", "ln((1)`)"); testDomainException("ln(-1)", "ln((1)`)");
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755"); testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
} }
@@ -100,8 +104,8 @@ public class CalculationTests {
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"); testOutput("7^(-sqrt2*17)", "(7^((sqrt(2)*17))`)", "4.81354609155297814551845300063563");
testEvalError("0^0", "(0^0)"); testDomainException("0^0", "(0^0)");
testEvalError("(-13)^.9999", "((13)`^.9999)"); testDomainException("(-13)^.9999", "((13)`^.9999)");
} }
} }

View File

@@ -9,6 +9,7 @@ import org.nwapw.abacus.function.*;
import org.nwapw.abacus.lexing.pattern.Match; import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer; import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.plugin.NumberImplementation;
import org.nwapw.abacus.plugin.Plugin; import org.nwapw.abacus.plugin.Plugin;
import org.nwapw.abacus.tree.TokenType; import org.nwapw.abacus.tree.TokenType;
@@ -16,16 +17,16 @@ import java.util.List;
public class TokenizerTests { public class TokenizerTests {
private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{})); private static Abacus abacus = new Abacus(new Configuration("precise", new String[]{}));
private static LexerTokenizer lexerTokenizer = new LexerTokenizer(); private static LexerTokenizer lexerTokenizer = new LexerTokenizer();
private static NumberFunction subtractFunction = new NumberFunction() { private static NumberFunction subtractFunction = new NumberFunction() {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2; return params.length == 2;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].subtract(params[1]); return params[0].subtract(params[1]);
} }
}; };
@@ -36,26 +37,26 @@ public class TokenizerTests {
0) { 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return true; return true;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return subtractFunction.apply(params); return subtractFunction.apply(implementation, params);
} }
}); });
registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0) { 0) {
@Override @Override
public boolean matchesParams(NumberInterface[] params) { public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return true; return true;
} }
@Override @Override
public NumberInterface applyInternal(NumberInterface[] params) { public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return subtractFunction.apply(params); return subtractFunction.apply(implementation, params);
} }
}); });
registerFunction("subtract", subtractFunction); registerFunction("subtract", subtractFunction);

View File

@@ -3,7 +3,8 @@
{% include head.html %} {% include head.html %}
<style> <style>
body { body {
margin-top: 100px; margin: 0px;
margin-top: 50px;
color: white; color: white;
text-align: center; text-align: center;
} }
@@ -11,7 +12,13 @@
font-family: "Source Code Pro" font-family: "Source Code Pro"
} }
img { img#logo {
margin: auto;
width: 100%;
max-width: 100px;
}
img#image_preview {
margin: auto; margin: auto;
width: 100%; width: 100%;
max-width: 432px; max-width: 432px;
@@ -31,8 +38,61 @@
background-color: #06e8a4; background-color: #06e8a4;
color: white; color: white;
} }
div.fullwidth {
width: 100%;
height: auto;
overflow: hidden;
}
div.fullwidth img {
max-width: 100%;
max-height: 450px;
margin: auto;
margin-top: 20px;
margin-bottom: 20px;
display: block;
border-radius: 5px;
}
div.white {
background-color: white;
color: black;
}
div.green {
background-color: #06e8a4;
color: white;
}
div.fullwidth div.double {
height: 100%;
text-align: left;
width: 50%;
box-sizing: border-box;
padding: 40px;
float: left;
background-color: inherit;
}
@media (max-width: 750px) {
div.fullwidth div.double {
width: 100%;
padding: 15px;
}
div.fullwidth img {
margin-top: 0px;
margin-bottom: 0px;
}
}
div.fullwidth div.double h1, h2, h3, h4, h5, h6 {
text-align: center;
}
</style> </style>
<body> <body>
<img src="https://raw.githubusercontent.com/DanilaFe/abacus/master/image/logo.png" id="logo">
<h1>Abacus</h1> <h1>Abacus</h1>
<h2>The programmer's calculator</h2> <h2>The programmer's calculator</h2>
<div id="buttons"> <div id="buttons">
@@ -41,6 +101,59 @@
<a class="button inverted" href="https://github.com/DanilaFe/abacus">Contribute</a> <a class="button inverted" href="https://github.com/DanilaFe/abacus">Contribute</a>
<a class="button inverted" href="https://github.com/DanilaFe/abacus/wiki">Wiki</a> <a class="button inverted" href="https://github.com/DanilaFe/abacus/wiki">Wiki</a>
</div> </div>
<img src="http://i.imgur.com/Min70QY.png" title="source: imgur.com" /> <img src="http://i.imgur.com/Min70QY.png" title="source: imgur.com" id="image_preview"/>
<h2>Features</h2>
<div class="fullwidth white">
<div class="double">
<img src="https://i.imgur.com/gmGJBBK.png">
</div>
<div class="double">
<h2>Precision</h2>
Abacus uses a mathematical tool called Taylor Series to determine values
as accurate as the user desires. Of course, this comes with some
performance issues with larger numbers. However, Abacus has been
tested to generate the value of e correctly to a thousand digits.
</div>
</div>
<div class="fullwidth green">
<div class="double">
<h2>Configurable and Customizable</h2>
The very first idea for Abacus was inspired by how difficult it was
to program a TI-84 calculator. Only two languages were available, TI-BASIC
and Assembly, the latter having virtually no documentation. Determined
to be better than a TI-84, Abacus implemented a plugin system that allows
users to easily create and add plugins written in the same programming
language as Abacus itself - Java. These plugins can access the full
power of the language, and implement their own ways of handling numbers,
as well as their own functions and even operators.<br><br>
Besides the ability to add plugins, Abacus also adds some general
options that can be used to make the user's experience more pleasant.
For instance, it allows for a computation limit to be set in order
to prevent excessively long evaluation: 8!!! is, for example, an expression
that even Wolfram Alpha doesn't compute accurately, and will never finish
on Abacus (it's simply too large). The computation limit will allow Abacus
to kill a computation if it takes too long. Support for user-definable
precision is also planned.
</div>
<div class="double">
<img src="https://i.imgur.com/JzenWPV.png">
</div>
</div>
<div class="fullwidth white">
<div class="double">
<img src="https://i.imgur.com/jY17I3A.png">
</div>
<div class="double">
<h2>Built-in Documentation</h2>
Abacus plugins are given a mechanism to register documentation for
the functions that they provide. The Abacus GUI displays these
functions in a searchable list, allowing the user to read the parameters
that have to be supplied to each function, as well as learn about
its return value.<br><br>
The search finds functions not only by their names, but also by relevant
terms mentioned in the function's description, thus allowing related
functions to be displayed together.
</div>
</div>
</body> </body>
</html> </html>

View File

@@ -7,7 +7,7 @@ $code-color: #efefef;
$accent-color: #00AFE8; $accent-color: #00AFE8;
$clear-color: white; $clear-color: white;
$title-font: "Open Sans"; $title-font: "Open Sans";
$text-font: Helvetica; $text-font: "Raleway";
$code-font: "Source Code Pro"; $code-font: "Source Code Pro";
$max-width: 850px; $max-width: 850px;

View File

@@ -1,6 +1,7 @@
apply plugin: 'application' apply plugin: 'application'
dependencies { dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
compile project(':core') compile project(':core')
} }

View File

@@ -14,8 +14,8 @@ import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Documentation; import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType; import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.number.ComputationInterruptedException; import org.nwapw.abacus.function.DomainException;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.*;
import org.nwapw.abacus.plugin.ClassFinder; import org.nwapw.abacus.plugin.ClassFinder;
import org.nwapw.abacus.plugin.PluginListener; import org.nwapw.abacus.plugin.PluginListener;
import org.nwapw.abacus.plugin.PluginManager; import org.nwapw.abacus.plugin.PluginManager;
@@ -153,6 +153,8 @@ public class AbacusController implements PluginListener {
return resultingString; return resultingString;
} catch (ComputationInterruptedException exception) { } catch (ComputationInterruptedException exception) {
return ERR_STOP; return ERR_STOP;
} catch (DomainException exception) {
return exception.getMessage();
} catch (RuntimeException exception) { } catch (RuntimeException exception) {
exception.printStackTrace(); exception.printStackTrace();
return ERR_EXCEPTION; return ERR_EXCEPTION;
@@ -195,7 +197,7 @@ public class AbacusController implements PluginListener {
*/ */
private final Runnable TIMER_RUNNABLE = () -> { private final Runnable TIMER_RUNNABLE = () -> {
try { try {
Configuration abacusConfig = abacus.getConfiguration(); ExtendedConfiguration abacusConfig = (ExtendedConfiguration) abacus.getConfiguration();
if (abacusConfig.getComputationDelay() == 0) return; if (abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000)); Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop(); performStop();
@@ -255,12 +257,12 @@ public class AbacusController implements PluginListener {
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true); if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
}); });
abacus = new Abacus(new Configuration(CONFIG_FILE)); abacus = new Abacus(new ExtendedConfiguration(CONFIG_FILE));
PluginManager abacusPluginManager = abacus.getPluginManager(); PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.addListener(this); abacusPluginManager.addListener(this);
performScan(); performScan();
computationLimitField.setText(Double.toString(abacus.getConfiguration().getComputationDelay())); computationLimitField.setText(Double.toString(((ExtendedConfiguration) abacus.getConfiguration()).getComputationDelay()));
computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> { computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.matches("(\\d+(\\.\\d*)?)?")) { if (!newValue.matches("(\\d+(\\.\\d*)?)?")) {
computationLimitField.setText(oldValue); computationLimitField.setText(oldValue);
@@ -337,8 +339,8 @@ public class AbacusController implements PluginListener {
if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName()); if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
} }
if (computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0) if (computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
configuration.setComputationDelay(Double.parseDouble(computationLimitField.getText())); ((ExtendedConfiguration) configuration).setComputationDelay(Double.parseDouble(computationLimitField.getText()));
configuration.saveTo(CONFIG_FILE); ((ExtendedConfiguration) configuration).saveTo(CONFIG_FILE);
changesMade = false; changesMade = false;
reloadAlertShown = false; reloadAlertShown = false;
} }

View File

@@ -0,0 +1,70 @@
package org.nwapw.abacus.fx
import com.moandjiezana.toml.Toml
import com.moandjiezana.toml.TomlWriter
import org.nwapw.abacus.config.Configuration
import java.io.File
/**
* Additional settings for user interface.
*
* ExtendedConfiguration is used to add other settings
* that aren't built into Abacus core, but are necessary
* for the fx module.
*
* @property computationDelay the delay before which the computation stops.
* @param implementation the number implementation, same as [Configuration.numberImplementation]
* @param disabledPlugins the list of plugins that should be disabled, same as [Configuration.disabledPlugins]
*/
class ExtendedConfiguration(var computationDelay: Double = 0.0,
implementation: String = "<default>",
disabledPlugins: Array<String> = emptyArray())
: Configuration(implementation, disabledPlugins) {
companion object {
/**
* The default TOML.
*/
val DEFAULT_TOML_STRING = """
computationDelay=0.0
implementation="naive"
disabledPlugins=[]
"""
/**
* A reader with the default TOML data.
*/
val DEFAULT_TOML_READER = Toml().read(DEFAULT_TOML_STRING)
/**
* A writer used to writing the configuration to disk.
*/
val DEFAULT_TOML_WRITER = TomlWriter()
}
/**
* Constructs a new configuration from a file on disk.
* @param tomlFile the file from disk to load.
*/
constructor(tomlFile: File) : this() {
copyFrom(Toml(DEFAULT_TOML_READER).read(tomlFile).to(ExtendedConfiguration::class.java))
}
/**
* Copies data from another configuration into this one.
* @param config the configuration to copy from.
*/
fun copyFrom(config: ExtendedConfiguration) {
computationDelay = config.computationDelay
numberImplementation = config.numberImplementation
disabledPlugins.clear()
disabledPlugins.addAll(config.disabledPlugins)
}
/**
* Saves this configuration to a file.
* @param file the file to save to.
*/
fun saveTo(file: File) {
DEFAULT_TOML_WRITER.write(this, file)
}
}

BIN
image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB