complex number support added

This commit is contained in:
serso 2011-09-18 00:08:04 +04:00
parent 5d4c42a0e7
commit 4d0ed38088
16 changed files with 458 additions and 143 deletions

22
pom.xml
View File

@ -15,8 +15,24 @@
<additionalLibs>${basedir}/src/misc/lib</additionalLibs> <additionalLibs>${basedir}/src/misc/lib</additionalLibs>
</properties> </properties>
<repositories>
<repository>
<id>congrace.de</id>
<name>releases</name>
<url>http://nexus.congrace.de/nexus/content/repositories/releases/</url>
<layout>default</layout>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.solovyev</groupId> <groupId>org.solovyev</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
@ -37,6 +53,12 @@
<systemPath>${additionalLibs}/jscl.jar</systemPath> <systemPath>${additionalLibs}/jscl.jar</systemPath>
</dependency> </dependency>
<dependency>
<groupId>de.congrace</groupId>
<artifactId>exp4j</artifactId>
<version>0.2.8</version>
</dependency>
<dependency> <dependency>
<groupId>com.intellij</groupId> <groupId>com.intellij</groupId>
<artifactId>annotations</artifactId> <artifactId>annotations</artifactId>

View File

@ -38,7 +38,7 @@
style="@style/control_button_style" style="@style/control_button_style"
a:onClick="eraseButtonClickHandler"/> a:onClick="eraseButtonClickHandler"/>
<TextView <org.solovyev.android.calculator.CalculatorDisplay
a:id="@+id/resultEditText" a:id="@+id/resultEditText"
style="@style/display_style" style="@style/display_style"
a:gravity="right|top" a:gravity="right|top"
@ -75,8 +75,8 @@
calc:textDown="acos" style="@style/digit_button_style" calc:textDown="acos" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/threeDigitButton" a:text="3" calc:textUp="tg" <org.solovyev.android.view.DirectionDragButton a:id="@+id/threeDigitButton" a:text="3" calc:textUp="tan"
calc:textDown="atg" style="@style/digit_button_style" calc:textDown="atan" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/multiplicationButton" a:text="×" <org.solovyev.android.view.DirectionDragButton a:id="@+id/multiplicationButton" a:text="×"
@ -106,20 +106,22 @@
<org.solovyev.android.view.DirectionDragButton a:id="@+id/fourDigitButton" <org.solovyev.android.view.DirectionDragButton a:id="@+id/fourDigitButton"
a:text="4" a:text="4"
calc:textUp="" calc:textUp="sinh"
calc:textDown="" calc:textDown="asinh"
style="@style/digit_button_style" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/fiveDigitButton" <org.solovyev.android.view.DirectionDragButton a:id="@+id/fiveDigitButton"
a:text="5" a:text="5"
calc:textUp="ln" calc:textUp="cosh"
calc:textDown="acosh"
style="@style/digit_button_style" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/sixDigitButton" a:text="6" <org.solovyev.android.view.DirectionDragButton a:id="@+id/sixDigitButton" a:text="6"
calc:textUp="" calc:textUp="tanh"
calc:textDown="" style="@style/digit_button_style" calc:textDown="atanh"
style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/plusButton" a:text="+" <org.solovyev.android.view.DirectionDragButton a:id="@+id/plusButton" a:text="+"
@ -147,12 +149,16 @@
<LinearLayout a:layout_weight="1" a:layout_width="match_parent" a:layout_height="0dp"> <LinearLayout a:layout_weight="1" a:layout_width="match_parent" a:layout_height="0dp">
<org.solovyev.android.view.DirectionDragButton a:id="@+id/sevenDigitButton" a:text="7" calc:textUp="" <org.solovyev.android.view.DirectionDragButton a:id="@+id/sevenDigitButton" a:text="7"
calc:textDown="" style="@style/digit_button_style" calc:textUp="i"
calc:textDown=""
style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/eightDigitButton" a:text="8" calc:textUp="" <org.solovyev.android.view.DirectionDragButton a:id="@+id/eightDigitButton" a:text="8"
calc:textDown="" style="@style/digit_button_style" calc:textUp="ln"
calc:textDown=""
style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/nineDigitButton" a:text="9" <org.solovyev.android.view.DirectionDragButton a:id="@+id/nineDigitButton" a:text="9"

View File

@ -33,7 +33,7 @@
style="@style/control_button_style" style="@style/control_button_style"
a:onClick="numericButtonClickHandler"/> a:onClick="numericButtonClickHandler"/>
<TextView <org.solovyev.android.calculator.CalculatorDisplay
a:id="@+id/resultEditText" a:id="@+id/resultEditText"
style="@style/display_style" style="@style/display_style"
a:gravity="right|top" a:gravity="right|top"
@ -56,8 +56,8 @@
calc:textDown="acos" style="@style/digit_button_style" calc:textDown="acos" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/threeDigitButton" a:text="3" calc:textUp="tg" <org.solovyev.android.view.DirectionDragButton a:id="@+id/threeDigitButton" a:text="3" calc:textUp="tan"
calc:textDown="atg" style="@style/digit_button_style" calc:textDown="atan" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/multiplicationButton" a:text="×" <org.solovyev.android.view.DirectionDragButton a:id="@+id/multiplicationButton" a:text="×"
@ -76,20 +76,22 @@
<org.solovyev.android.view.DirectionDragButton a:id="@+id/fourDigitButton" <org.solovyev.android.view.DirectionDragButton a:id="@+id/fourDigitButton"
a:text="4" a:text="4"
calc:textUp="" calc:textUp="sinh"
calc:textDown="" calc:textDown="asinh"
style="@style/digit_button_style" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/fiveDigitButton" <org.solovyev.android.view.DirectionDragButton a:id="@+id/fiveDigitButton"
a:text="5" a:text="5"
calc:textUp="ln" calc:textUp="cosh"
calc:textDown="acosh"
style="@style/digit_button_style" style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/sixDigitButton" a:text="6" <org.solovyev.android.view.DirectionDragButton a:id="@+id/sixDigitButton" a:text="6"
calc:textUp="" calc:textUp="tanh"
calc:textDown="" style="@style/digit_button_style" calc:textDown="atanh"
style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/divisionButton" a:text="/" <org.solovyev.android.view.DirectionDragButton a:id="@+id/divisionButton" a:text="/"
@ -106,12 +108,16 @@
<LinearLayout a:layout_weight="1" a:layout_width="match_parent" a:layout_height="0dp"> <LinearLayout a:layout_weight="1" a:layout_width="match_parent" a:layout_height="0dp">
<org.solovyev.android.view.DirectionDragButton a:id="@+id/sevenDigitButton" a:text="7" calc:textUp="" <org.solovyev.android.view.DirectionDragButton a:id="@+id/sevenDigitButton" a:text="7"
calc:textDown="" style="@style/digit_button_style" calc:textUp="i"
calc:textDown=""
style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/eightDigitButton" a:text="8" calc:textUp="" <org.solovyev.android.view.DirectionDragButton a:id="@+id/eightDigitButton" a:text="8"
calc:textDown="" style="@style/digit_button_style" calc:textUp="ln"
calc:textDown=""
style="@style/digit_button_style"
a:onClick="digitButtonClickHandler"/> a:onClick="digitButtonClickHandler"/>
<org.solovyev.android.view.DirectionDragButton a:id="@+id/nineDigitButton" a:text="9" <org.solovyev.android.view.DirectionDragButton a:id="@+id/nineDigitButton" a:text="9"

View File

@ -2,7 +2,8 @@
<resources> <resources>
<string name="c_app_name">Calculator</string> <string name="c_app_name">Calculator</string>
<string name="c_app_settings">Calculator</string> <string name="c_app_settings">Calculator</string>
<string name="syntax_error">Syntax error</string> <string name="c_syntax_error">Syntax error</string>
<string name="c_result_copied">Result copied to clipboard!</string>
<string name="c_settings">Settings</string> <string name="c_settings">Settings</string>
<string name="c_help">Help</string> <string name="c_help">Help</string>
<string name="c_prefs_main_category">Main settings</string> <string name="c_prefs_main_category">Main settings</string>

View File

@ -27,7 +27,7 @@
</style> </style>
<style name="display_style"> <style name="display_style">
<item name="android:textSize">30dp</item> <item name="android:textSize">25dp</item>
<item name="android:background">#000000</item> <item name="android:background">#000000</item>
<item name="android:textColor">#ffffff</item> <item name="android:textColor">#ffffff</item>
<item name="android:gravity">left|top</item> <item name="android:gravity">left|top</item>

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* User: serso
* Date: 9/17/11
* Time: 10:58 PM
*/
public class CalculatorDisplay extends TextView {
private boolean valid = true;
public CalculatorDisplay(Context context) {
super(context);
}
public CalculatorDisplay(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CalculatorDisplay(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
setValid(true);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 9/17/11
* Time: 11:05 PM
*/
public class CalculatorDisplayHistoryState extends EditorHistoryState {
private boolean valid = true;
public CalculatorDisplayHistoryState() {
}
public CalculatorDisplayHistoryState(boolean valid) {
this.valid = valid;
}
public CalculatorDisplayHistoryState(int cursorPosition, @Nullable String text, boolean valid) {
super(cursorPosition, text);
this.valid = valid;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
}

View File

@ -18,9 +18,9 @@ public class CalculatorHistoryState {
private EditorHistoryState editorState; private EditorHistoryState editorState;
@NotNull @NotNull
private EditorHistoryState displayState; private CalculatorDisplayHistoryState displayState;
public CalculatorHistoryState(@NotNull EditorHistoryState editorState, @NotNull EditorHistoryState displayState) { public CalculatorHistoryState(@NotNull EditorHistoryState editorState, @NotNull CalculatorDisplayHistoryState displayState) {
this.editorState = editorState; this.editorState = editorState;
this.displayState = displayState; this.displayState = displayState;
} }
@ -35,11 +35,11 @@ public class CalculatorHistoryState {
} }
@NotNull @NotNull
public EditorHistoryState getDisplayState() { public CalculatorDisplayHistoryState getDisplayState() {
return displayState; return displayState;
} }
public void setDisplayState(@NotNull EditorHistoryState displayState) { public void setDisplayState(@NotNull CalculatorDisplayHistoryState displayState) {
this.displayState = displayState; this.displayState = displayState;
} }
} }

View File

@ -5,13 +5,13 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import android.util.Log;
import bsh.EvalError; import bsh.EvalError;
import bsh.Interpreter; import bsh.Interpreter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.exceptions.SersoException; import org.solovyev.common.exceptions.SersoException;
import org.solovyev.common.utils.MathUtils; import org.solovyev.common.utils.MathUtils;
import org.solovyev.common.utils.StringUtils;
import org.solovyev.util.math.Complex;
/** /**
* User: serso * User: serso
@ -32,28 +32,76 @@ public class CalculatorModel {
interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands")); interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
} }
public String evaluate(@NotNull JsclOperation operation, @NotNull String expression ) throws EvalError, ParseException { public String evaluate(@NotNull JsclOperation operation, @NotNull String expression) throws EvalError, ParseException {
final String preprocessedExpression = Preprocessor.process(expression); final String preprocessedExpression = Preprocessor.process(expression);
Log.d(CalculatorModel.class.getName(), "Preprocessed expression: " + preprocessedExpression); //Log.d(CalculatorModel.class.getName(), "Preprocessed expression: " + preprocessedExpression);
Object evaluationObject = interpreter.eval(Preprocessor.wrap(operation, preprocessedExpression)); Object evaluationObject = interpreter.eval(Preprocessor.wrap(operation, preprocessedExpression));
String result = String.valueOf(evaluationObject).trim(); String result = String.valueOf(evaluationObject).trim();
try { try {
final Double dResult = Double.valueOf(result); result = round(result);
result = String.valueOf(MathUtils.round(dResult, NUMBER_OF_FRACTION_DIGITS));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new ParseException(e); if (result.contains("sqrt(-1)")) {
try {
result = createResultForComplexNumber(result.replace("sqrt(-1)", "i"));
} catch (NumberFormatException e1) {
// throw original one
throw new ParseException(e);
}
} else {
throw new ParseException(e);
}
} }
return result; return result;
} }
public String createResultForComplexNumber(@NotNull final String s) {
final Complex complex = new Complex();
String result = "";
// may be it's just complex number
int plusIndex = s.lastIndexOf("+");
if (plusIndex >= 0) {
complex.setReal(round(s.substring(0, plusIndex)));
result += complex.getReal();
result += "+";
} else {
plusIndex = s.lastIndexOf("-");
if (plusIndex >= 0) {
complex.setReal(round(s.substring(0, plusIndex)));
result += complex.getReal();
result += "-";
}
}
int multiplyIndex = s.indexOf("*");
if (multiplyIndex >= 0) {
complex.setImag(round(s.substring(plusIndex >= 0 ? plusIndex+1 : 0, multiplyIndex)));
result += complex.getImag();
}
result += "i";
return result;
}
private String round(@NotNull String result) {
final Double dResult = Double.valueOf(result);
result = String.valueOf(MathUtils.round(dResult, NUMBER_OF_FRACTION_DIGITS));
return result;
}
public static class ParseException extends SersoException { public static class ParseException extends SersoException {
public ParseException(Throwable cause) { public ParseException(Throwable cause) {
super(cause); super(cause);
} }
} }
} }

View File

@ -21,7 +21,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.CursorControl; import org.solovyev.android.view.CursorControl;
import org.solovyev.android.view.HistoryControl; import org.solovyev.android.view.HistoryControl;
import org.solovyev.common.math.calculators.Calculator;
import org.solovyev.common.utils.MutableObject; import org.solovyev.common.utils.MutableObject;
import org.solovyev.common.utils.StringUtils; import org.solovyev.common.utils.StringUtils;
import org.solovyev.common.utils.history.HistoryAction; import org.solovyev.common.utils.history.HistoryAction;
@ -37,13 +36,13 @@ import org.solovyev.util.math.MathEntityType;
public class CalculatorView implements CursorControl, HistoryControl<CalculatorHistoryState> { public class CalculatorView implements CursorControl, HistoryControl<CalculatorHistoryState> {
// millis to wait before evaluation after user edit action // millis to wait before evaluation after user edit action
public static final int EVAL_DELAY_MILLIS = 1000; public static final int EVAL_DELAY_MILLIS = 500;
@NotNull @NotNull
private final CalculatorEditText editor; private final CalculatorEditText editor;
@NotNull @NotNull
private final TextView display; private final CalculatorDisplay display;
@NotNull @NotNull
private final Activity activity; private final Activity activity;
@ -52,7 +51,7 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
private final CalculatorModel calculatorModel; private final CalculatorModel calculatorModel;
@NotNull @NotNull
private HistoryHelper<CalculatorHistoryState> history; private final HistoryHelper<CalculatorHistoryState> history;
public CalculatorView(@NotNull final Activity activity, @NotNull CalculatorModel calculator) { public CalculatorView(@NotNull final Activity activity, @NotNull CalculatorModel calculator) {
this.activity = activity; this.activity = activity;
@ -64,15 +63,17 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
this.editor.setInputType(InputType.TYPE_NULL); this.editor.setInputType(InputType.TYPE_NULL);
imm.hideSoftInputFromWindow(this.editor.getWindowToken(), 0); imm.hideSoftInputFromWindow(this.editor.getWindowToken(), 0);
this.display = (TextView) activity.findViewById(R.id.resultEditText); this.display = (CalculatorDisplay) activity.findViewById(R.id.resultEditText);
this.display.setOnClickListener(new View.OnClickListener() { this.display.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
final CharSequence text = ((TextView) v).getText(); if (((CalculatorDisplay) v).isValid()) {
if (!StringUtils.isEmpty(text)) { final CharSequence text = ((TextView) v).getText();
final ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Activity.CLIPBOARD_SERVICE); if (!StringUtils.isEmpty(text)) {
clipboard.setText(text); final ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Activity.CLIPBOARD_SERVICE);
Toast.makeText(activity, "Result copied to clipboard!", Toast.LENGTH_SHORT).show(); clipboard.setText(text);
Toast.makeText(activity, "Result copied to clipboard!", Toast.LENGTH_SHORT).show();
}
} }
} }
}); });
@ -120,17 +121,21 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
currentRunner.setObject(new Runnable() { currentRunner.setObject(new Runnable() {
@Override @Override
public void run() { public void run() {
// allow only one runner at one time
synchronized (currentRunner) { synchronized (currentRunner) {
// do only if nothing was post delayed before current instance was posted //lock all operations with history
if (currentRunner.getObject() == this) { synchronized (history) {
// actually nothing shall be logged while text operations are done // do only if nothing was post delayed before current instance was posted
evaluate(editorStateAfter, true); if (currentRunner.getObject() == this) {
// actually nothing shall be logged while text operations are done
evaluate(editorStateAfter);
if (history.isUndoAvailable()) { if (history.isUndoAvailable()) {
history.undo(getCurrentHistoryState()); history.undo(getCurrentHistoryState());
}
saveHistoryState();
} }
saveHistoryState();
} }
} }
} }
@ -142,29 +147,26 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
} }
} }
private void evaluate(@Nullable final String expression, final boolean showError) { private void evaluate(@Nullable final String expression) {
if (!StringUtils.isEmpty(expression)) { if (!StringUtils.isEmpty(expression)) {
final TextView localDisplay = display; final CalculatorDisplay localDisplay = display;
final Activity localActivity = activity;
try { try {
Log.d(CalculatorView.class.getName(), "Trying to evaluate: " + expression); Log.d(CalculatorView.class.getName(), "Trying to evaluate: " + expression);
localDisplay.setText(calculatorModel.evaluate(JsclOperation.numeric, expression)); localDisplay.setText(calculatorModel.evaluate(JsclOperation.numeric, expression));
} catch (EvalError e) { } catch (EvalError e) {
handleEvaluationException(expression, showError, localDisplay, localActivity, e); handleEvaluationException(expression, localDisplay, e);
} catch (CalculatorModel.ParseException e) { } catch (CalculatorModel.ParseException e) {
handleEvaluationException(expression, showError, localDisplay, localActivity, e); handleEvaluationException(expression, localDisplay, e);
} }
} }
} }
private void handleEvaluationException(@NotNull String expression, boolean showError, @NotNull TextView localDisplay, @NotNull Activity localActivity, @NotNull Exception e) { private void handleEvaluationException(@NotNull String expression, @NotNull CalculatorDisplay localDisplay, @NotNull Exception e) {
Log.d(CalculatorView.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e.getMessage()); Log.d(CalculatorView.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e.getMessage());
localDisplay.setText(""); localDisplay.setText(R.string.c_syntax_error);
if (showError) { localDisplay.setValid(false);
Toast.makeText(localActivity, R.string.syntax_error, Toast.LENGTH_SHORT).show();
}
} }
public void clear() { public void clear() {
@ -176,7 +178,7 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
} }
public void evaluate() { public void evaluate() {
evaluate(editor.getText().toString(), true); evaluate(editor.getText().toString());
} }
public void processDigitButtonAction(@Nullable final String text) { public void processDigitButtonAction(@Nullable final String text) {
@ -223,18 +225,27 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
@Override @Override
public void doHistoryAction(@NotNull HistoryAction historyAction) { public void doHistoryAction(@NotNull HistoryAction historyAction) {
if (history.isActionAvailable(historyAction)) { synchronized (history) {
final CalculatorHistoryState newState = history.doAction(historyAction, getCurrentHistoryState()); if (history.isActionAvailable(historyAction)) {
if (newState != null) { final CalculatorHistoryState newState = history.doAction(historyAction, getCurrentHistoryState());
setCurrentHistoryState(newState); if (newState != null) {
setCurrentHistoryState(newState);
}
} }
} }
} }
@Override @Override
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) { public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
setValuesFromHistory(this.editor, editorHistoryState.getEditorState()); synchronized (history) {
setValuesFromHistory(this.display, editorHistoryState.getDisplayState()); setValuesFromHistory(this.editor, editorHistoryState.getEditorState());
setValuesFromHistory(this.display, editorHistoryState.getDisplayState());
}
}
private void setValuesFromHistory(@NotNull CalculatorDisplay display, CalculatorDisplayHistoryState editorHistoryState) {
setValuesFromHistory(display, (EditorHistoryState)editorHistoryState);
display.setValid(editorHistoryState.isValid());
} }
private void setValuesFromHistory(@NotNull TextView editText, EditorHistoryState editorHistoryState) { private void setValuesFromHistory(@NotNull TextView editText, EditorHistoryState editorHistoryState) {
@ -247,7 +258,9 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
@Override @Override
@NotNull @NotNull
public CalculatorHistoryState getCurrentHistoryState() { public CalculatorHistoryState getCurrentHistoryState() {
return new CalculatorHistoryState(getEditorHistoryState(this.editor), getEditorHistoryState(this.display)); synchronized (history) {
return new CalculatorHistoryState(getEditorHistoryState(this.editor), getCalculatorDisplayHistoryState(this.display));
}
} }
private EditorHistoryState getEditorHistoryState(@NotNull TextView textView) { private EditorHistoryState getEditorHistoryState(@NotNull TextView textView) {
@ -258,4 +271,14 @@ public class CalculatorView implements CursorControl, HistoryControl<CalculatorH
return result; return result;
} }
private CalculatorDisplayHistoryState getCalculatorDisplayHistoryState(@NotNull CalculatorDisplay display) {
final CalculatorDisplayHistoryState result = new CalculatorDisplayHistoryState();
result.setText(String.valueOf(display.getText()));
result.setCursorPosition(display.getSelectionStart());
result.setValid(display.isValid());
return result;
}
} }

View File

@ -6,6 +6,11 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.CollectionsUtils;
import org.solovyev.common.utils.EqualsFinder;
import org.solovyev.common.utils.Finder;
import org.solovyev.util.math.Functions;
import org.solovyev.util.math.MathEntityType; import org.solovyev.util.math.MathEntityType;
public class Preprocessor { public class Preprocessor {
@ -14,49 +19,75 @@ public class Preprocessor {
public static String process(@NotNull String s) { public static String process(@NotNull String s) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final StartWithFinder startsWithFinder = new StartWithFinder(s);
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i); char ch = s.charAt(i);
checkMultiplicationSignBeforeFunction(sb, s, i); checkMultiplicationSignBeforeFunction(sb, s, i);
if (ch == '[' || ch == '{') { if (MathEntityType.openGroupSymbols.contains(ch)) {
sb.append('('); sb.append('(');
} else if (ch == ']' || ch == '}') { } else if (MathEntityType.closeGroupSymbols.contains(ch)) {
sb.append(')'); sb.append(')');
} else if (ch == 'π') { } else if (ch == 'π') {
sb.append("pi"); sb.append("pi");
} else if (ch == '×' || ch == '∙') { } else if (ch == '×' || ch == '∙') {
sb.append("*"); sb.append("*");
} else if (s.startsWith("ln", i)) {
sb.append("log");
i += 1;
} else if (s.startsWith("tg", i)) {
sb.append("tan");
i += 1;
} else if (s.startsWith("atg", i)) {
sb.append("atan");
i += 2;
} else if (s.startsWith("acos", i)) {
sb.append("acos");
i += 3;
} else if (s.startsWith("asin", i)) {
sb.append("asin");
i += 3;
} else if (s.startsWith("e(", i)) {
sb.append("exp(");
i += 1;
} else if (ch == 'e') {
sb.append("exp(1)");
} else if (ch == '√') {
sb.append("sqrt");
} else { } else {
sb.append(ch); startsWithFinder.setI(i);
final String function = CollectionsUtils.get(MathEntityType.functions, startsWithFinder);
if (function != null) {
sb.append(toJsclFunction(function));
i += function.length() - 1;
} else if (ch == 'e') {
sb.append("exp(1)");
} else if (ch == 'i') {
sb.append("sqrt(-1)");
} else {
sb.append(ch);
}
} }
} }
return sb.toString(); return sb.toString();
} }
@NotNull
private static String toJsclFunction(@NotNull String function) {
final String result;
if (function.equals(Functions.LN)) {
result = Functions.LOG;
} else if (function.equals(Functions.SQRT_SIGN)) {
result = Functions.SQRT;
} else {
result = function;
}
return result;
}
private static class StartWithFinder implements Finder<String> {
private int i;
@NotNull
private final String targetString;
private StartWithFinder(@NotNull String targetString) {
this.targetString = targetString;
}
@Override
public boolean isFound(@Nullable String s) {
return targetString.startsWith(s, i);
}
public void setI(int i) {
this.i = i;
}
}
private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) { private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) {
if (i > 0) { if (i > 0) {
// get character before function // get character before function

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.util.math;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 9/17/11
* Time: 11:35 PM
*/
public class Complex {
@Nullable
private String real, imag;
@Nullable
public String getReal() {
return real;
}
public void setReal(@Nullable String real) {
this.real = real;
}
@Nullable
public String getImag() {
return imag;
}
public void setImag(@Nullable String imag) {
this.imag = imag;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.util.math;
import org.jetbrains.annotations.NonNls;
import java.util.Arrays;
import java.util.List;
/**
* User: serso
* Date: 9/17/11
* Time: 10:01 PM
*/
public interface Functions {
String SIN = "sin";
String SINH = "sinh";
String ASIN = "asin";
String ASINH = "asinh";
String COS = "cos";
String COSH = "cosh";
String ACOS = "acos";
String ACOSH = "acosh";
String TAN = "tan";
String TANH = "tanh";
String ATAN = "atan";
String ATANH = "atanh";
String LOG = "log";
String LN = "ln";
String MOD = "mod";
String EXP = "exp";
String SQRT_SIGN = "";
String SQRT = "sqrt";
public static final List<String> all = Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP);
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.util.math;
import org.jetbrains.annotations.NotNull;
public enum MathEntity {
minus("-"),
equals("="),
factorial("!"),
plus("+"),
multiply("*"),
divide("/"),
power("^"),
sin("sin"),
asin("asin"),
cos("cos"),
acos("acos"),
tg("tg"),
atg("atg"),
exp("exp"),
log("log"),
ln("ln"),
mod("mod"),
sqrt("sqrt");
@NotNull
private final String text;
private MathEntity (@NotNull String text) {
this.text = text;
}
@NotNull
public String getText() {
return text;
}
}

View File

@ -24,7 +24,7 @@ public enum MathEntityType {
group_symbols, group_symbols,
group_symbol; group_symbol;
public static final List<Character> constants = Arrays.asList('e', 'π'); public static final List<Character> constants = Arrays.asList('e', 'π', 'i');
public static final List<Character> dots = Arrays.asList('.', ','); public static final List<Character> dots = Arrays.asList('.', ',');
@ -32,7 +32,7 @@ public enum MathEntityType {
public static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' ); public static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' );
public static final List<String> functions = Arrays.asList("sin", "asin", "cos", "acos", "tg", "atg", "log", "ln", "mod", ""); public static final List<String> functions = Functions.all;
public static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}"); public static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}");

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import de.congrace.exp4j.Calculable;
import de.congrace.exp4j.ExpressionBuilder;
import de.congrace.exp4j.UnknownFunctionException;
import de.congrace.exp4j.UnparsableExpressionException;
import org.junit.Assert;
import org.junit.Test;
/**
* User: serso
* Date: 9/17/11
* Time: 9:47 PM
*/
public class CalculatorModelTest {
@Test
public void testEvaluate() throws Exception {
final CalculatorModel cm = new CalculatorModel();
Assert.assertEquals("4.0", cm.evaluate(JsclOperation.numeric, "2+2"));
Assert.assertEquals("-0.7568", cm.evaluate(JsclOperation.numeric, "sin(4)"));
Assert.assertEquals("0.5236", cm.evaluate(JsclOperation.numeric, "asin(0.5)"));
Assert.assertEquals("-0.39626", cm.evaluate(JsclOperation.numeric, "sin(4)asin(0.5)"));
Assert.assertEquals("-0.5604", cm.evaluate(JsclOperation.numeric, "sin(4)asin(0.5)sqrt(2)"));
Assert.assertEquals("-0.5604", cm.evaluate(JsclOperation.numeric, "sin(4)asin(0.5)√(2)"));
Assert.assertEquals("7.38906", cm.evaluate(JsclOperation.numeric, "e^2"));
Assert.assertEquals("7.38906", cm.evaluate(JsclOperation.numeric, "exp(1)^2"));
Assert.assertEquals("7.38906", cm.evaluate(JsclOperation.numeric, "exp(2)"));
Assert.assertEquals("2.0+i", cm.evaluate(JsclOperation.numeric, "2*1+sqrt(-1)"));
Assert.assertEquals("0.92054+3.14159i", cm.evaluate(JsclOperation.numeric, "ln(5cosh(38π√(2cos(2))))"));
Assert.assertEquals("7.38906i", cm.evaluate(JsclOperation.numeric, "iexp(2)"));
Assert.assertEquals("2.0+7.38906i", cm.evaluate(JsclOperation.numeric, "2+iexp(2)"));
Assert.assertEquals("2.0+7.38906i", cm.evaluate(JsclOperation.numeric, "2+√(-1)exp(2)"));
Assert.assertEquals("2.0-2.5i", cm.evaluate(JsclOperation.numeric, "2-2.5i"));
Assert.assertEquals("-2.0-2.5i", cm.evaluate(JsclOperation.numeric, "-2-2.5i"));
Assert.assertEquals("-2.0+2.5i", cm.evaluate(JsclOperation.numeric, "-2+2.5i"));
Assert.assertEquals("-2.0+2.1i", cm.evaluate(JsclOperation.numeric, "-2+2.1i"));
Assert.assertEquals("-3.41007+3.41007i", cm.evaluate(JsclOperation.numeric, "(5tan(2i)+2i)/(1-i)"));
Assert.assertEquals("-0.1-0.2i", cm.evaluate(JsclOperation.numeric, "(1-i)/(2+6i)"));
}
@Test
public void testComplexNumbers() throws Exception {
final CalculatorModel cm = new CalculatorModel();
Assert.assertEquals("1.22133+23123.0i", cm.createResultForComplexNumber("1.22133232+23123*i"));
Assert.assertEquals("1.22133+1.2i", cm.createResultForComplexNumber("1.22133232+1.2*i"));
Assert.assertEquals("1.22i", cm.createResultForComplexNumber("1.22*i"));
Assert.assertEquals("i", cm.createResultForComplexNumber("i"));
}
}