Skip to content
Browse files

Update unit tests to be Android JUnit and work with refactored code

  • Loading branch information...
1 parent 6e030ff commit ad0277c1678357eadecebbf2219cfd8a60b622aa @lpar committed
View
635 src/com/ath0/rpn/CalculatorStack.java
@@ -17,330 +17,339 @@
*/
public class CalculatorStack implements Serializable {
- /**
- * Object version for serialization.
- */
- private static final long serialVersionUID = 1L;
-
- // Number of characters to preallocate when converting a stack value into a
- // string or storing input from user.
- private static final int TYPICAL_LENGTH = 32;
- // 4x the above
- private static final int TYPICAL_LENGTH_X4 = 128;
-
- // How many digits of precision (decimal places) are used internally in
- // calculations.
- private static final int INTERNAL_SCALE = 32;
-
- private final Stack<BigDecimal> stack;
+ /**
+ * Object version for serialization.
+ */
+ private static final long serialVersionUID = 1L;
+
+ // Number of characters to preallocate when converting a stack value into a
+ // string or storing input from user.
+ private static final int TYPICAL_LENGTH = 32;
+ // 4x the above
+ private static final int TYPICAL_LENGTH_X4 = 128;
+
+ // How many digits of precision (decimal places) are used internally in
+ // calculations.
+ private static final int INTERNAL_SCALE = 32;
+
+ private final Stack<BigDecimal> stack;
// Initial scale is 2 decimal places, as that's the most useful for general
- // everyday calculations.
- private int scale = 2;
-
- public CalculatorStack() {
- super();
- this.stack = new Stack<BigDecimal>();
- }
-
- /**
- * Pushes a value onto the stack.
- * @param number A valid decimal number, in a String. Usually taken from the
- * InputBuffer.
- */
- public void push(final String number) {
- final BigDecimal newnum = new BigDecimal(number);
- this.stack.push(newnum);
- }
-
- /**
- * Returns whether the stack is empty.
- */
- public boolean isEmpty() {
- return this.stack.isEmpty();
- }
-
- /**
- * Gets the contents of the stack as a string.
- * @param levels the number of levels of stack to return
- * @return a text representation of the stack
- */
- public StringBuilder toString(final int levels) {
- final StringBuilder result = new StringBuilder(TYPICAL_LENGTH_X4);
- if (this.stack != null) {
- final int depth = this.stack.size();
- for (int i = 0; i < levels; i++) {
- if (i != 0) {
- result.append('\n');
- }
- final int idx = depth - levels + i;
- if (idx >= 0) {
- result.append(formatNumber(this.stack.get(idx)));
- }
- }
- }
- return result;
- }
-
- /**
- * Formats a BigDecimal number to a fixed number of decimal places, and adds
- * thousands commas.
- * @param number
- * @return
- */
- private String formatNumber(final BigDecimal number) {
- final StringBuilder result = new StringBuilder(TYPICAL_LENGTH);
- result.append(number.setScale(this.scale,
- RoundingMode.HALF_UP).toPlainString());
- if (this.scale > 0) {
- if (result.indexOf(".") == -1) {
- result.append('.');
- }
- final int zerosAfterPoint = result.length() - result.indexOf(".") - 1;
- for (int i = zerosAfterPoint; i < this.scale; i++) {
- result.append('0');
- }
- }
- // Add commas
- int dot = result.indexOf(".");
- if (dot < 1) {
- dot = result.length();
- }
- int lowindex = 0;
- if (result.charAt(0) == '-') {
- lowindex = 1;
- }
- for (int i = dot - 3; i > lowindex; i -= 3) {
- result.insert(i, ',');
- }
- return result.toString();
- }
-
- /**
- * Changes the sign of the top number on the stack.
- */
- public void chs() {
- if (!this.stack.isEmpty()) {
- final BigDecimal topnum = this.stack.pop();
- this.stack.push(topnum.negate());
- }
- }
-
- /**
- * Drops the top element from the stack.
- */
- public void drop() {
- if (!this.stack.isEmpty()) {
- this.stack.pop();
- }
- }
-
- /**
- * Duplicates the top element on the stack.
- */
- public void dup() {
- if (!this.stack.isEmpty()) {
- final BigDecimal topnum = this.stack.peek();
- this.stack.push(topnum);
- }
- }
-
+ // everyday calculations.
+ private int scale = 2;
+
+ public CalculatorStack() {
+ super();
+ this.stack = new Stack<BigDecimal>();
+ }
+
+ /**
+ * Pushes a value onto the stack.
+ * @param number A valid decimal number, in a String. Usually taken from the
+ * InputBuffer.
+ */
+ public void push(final String number) {
+ final BigDecimal newnum = new BigDecimal(number);
+ this.stack.push(newnum);
+ }
+
+ /**
+ * Returns whether the stack is empty.
+ */
+ public boolean isEmpty() {
+ return this.stack.isEmpty();
+ }
+
+ /**
+ * Gets the contents of the stack as a string.
+ * @param levels the number of levels of stack to return
+ * @return a text representation of the stack
+ */
+ public StringBuilder toString(final int levels) {
+ final StringBuilder result = new StringBuilder(TYPICAL_LENGTH_X4);
+ if (this.stack != null) {
+ final int depth = this.stack.size();
+ for (int i = 0; i < levels; i++) {
+ if (i != 0) {
+ result.append('\n');
+ }
+ final int idx = depth - levels + i;
+ if (idx >= 0) {
+ result.append(formatNumber(this.stack.get(idx)));
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get value without thousands commas for unit tests, to avoid needing to
+ * implement formatNumber there.
+ */
+ @Override
+ public String toString() {
+ return this.toString(1).toString().replaceAll(",", "");
+ }
+
+ /**
+ * Formats a BigDecimal number to a fixed number of decimal places, and adds
+ * thousands commas.
+ * @param number
+ * @return
+ */
+ private String formatNumber(final BigDecimal number) {
+ final StringBuilder result = new StringBuilder(TYPICAL_LENGTH);
+ result.append(number.setScale(this.scale,
+ RoundingMode.HALF_UP).toPlainString());
+ if (this.scale > 0) {
+ if (result.indexOf(".") == -1) {
+ result.append('.');
+ }
+ final int zerosAfterPoint = result.length() - result.indexOf(".") - 1;
+ for (int i = zerosAfterPoint; i < this.scale; i++) {
+ result.append('0');
+ }
+ }
+ // Add commas
+ int dot = result.indexOf(".");
+ if (dot < 1) {
+ dot = result.length();
+ }
+ int lowindex = 0;
+ if (result.charAt(0) == '-') {
+ lowindex = 1;
+ }
+ for (int i = dot - 3; i > lowindex; i -= 3) {
+ result.insert(i, ',');
+ }
+ return result.toString();
+ }
+
+ /**
+ * Changes the sign of the top number on the stack.
+ */
+ public void chs() {
+ if (!this.stack.isEmpty()) {
+ final BigDecimal topnum = this.stack.pop();
+ this.stack.push(topnum.negate());
+ }
+ }
+
+ /**
+ * Drops the top element from the stack.
+ */
+ public void drop() {
+ if (!this.stack.isEmpty()) {
+ this.stack.pop();
+ }
+ }
+
+ /**
+ * Duplicates the top element on the stack.
+ */
+ public void dup() {
+ if (!this.stack.isEmpty()) {
+ final BigDecimal topnum = this.stack.peek();
+ this.stack.push(topnum);
+ }
+ }
+
/**
* Swaps the top two elements on the stack.
*/
- public void swap() {
- if (this.stack.size() > 1) {
- final BigDecimal x = this.stack.pop();
- final BigDecimal y = this.stack.pop();
- this.stack.push(x);
- this.stack.push(y);
- }
- }
-
- /**
- * Adds together the top two elements on the stack, and replaces them with
- * the result.
- */
- public void add() {
- if (this.stack.size() > 1) {
- final BigDecimal x = this.stack.pop();
- final BigDecimal y = this.stack.pop();
- final BigDecimal r = y.add(x);
- this.stack.push(r);
- }
- }
-
- /**
- * Subtracts the top number on the stack from the number beneath it, and
- * replaces them both with the result.
- */
- public void subtract() {
- if (this.stack.size() > 1) {
- BigDecimal x = this.stack.pop();
- BigDecimal y = this.stack.pop();
- BigDecimal r = y.subtract(x);
- this.stack.push(r);
- }
- }
-
- /**
- * Multiplies the top two numbers on the stack together, and replaces them
- * with the result.
- */
- public void multiply() {
- if (this.stack.size() > 1) {
- BigDecimal x = this.stack.pop();
- BigDecimal y = this.stack.pop();
- BigDecimal r = y.multiply(x);
- this.stack.push(r);
- }
- }
-
- /**
- * Takes the top item on the stack, and uses its integer value as the power
- * for raising the number beneath it.
- * e.g. before: X Y after: X^Y before: 2 3 after: 8
- * @return an error message, or null if there is no error
- */
- // Returns error message, or null if no error.
- public String power() {
- String result = null;
- if (this.stack.size() > 1) {
- BigDecimal y = this.stack.pop();
- BigDecimal x = this.stack.pop();
- try {
- int yi = y.intValueExact();
- BigDecimal r = x.pow(yi);
- this.stack.push(r);
- } catch (ArithmeticException e) {
- result = "Value out of range";
- }
- }
- return result;
- }
-
- /**
- * Uses the top number on the stack to divide the number beneath it.
- * Replaces both with the result of the division.
- * e.g. before: x y after: x/y before: 4 2 after: 2
+ public void swap() {
+ if (this.stack.size() > 1) {
+ final BigDecimal x = this.stack.pop();
+ final BigDecimal y = this.stack.pop();
+ this.stack.push(x);
+ this.stack.push(y);
+ }
+ }
+
+ /**
+ * Adds together the top two elements on the stack, and replaces them with
+ * the result.
+ */
+ public void add() {
+ if (this.stack.size() > 1) {
+ final BigDecimal x = this.stack.pop();
+ final BigDecimal y = this.stack.pop();
+ final BigDecimal r = y.add(x);
+ this.stack.push(r);
+ }
+ }
+
+ /**
+ * Subtracts the top number on the stack from the number beneath it, and
+ * replaces them both with the result.
+ */
+ public void subtract() {
+ if (this.stack.size() > 1) {
+ BigDecimal x = this.stack.pop();
+ BigDecimal y = this.stack.pop();
+ BigDecimal r = y.subtract(x);
+ this.stack.push(r);
+ }
+ }
+
+ /**
+ * Multiplies the top two numbers on the stack together, and replaces them
+ * with the result.
+ */
+ public void multiply() {
+ if (this.stack.size() > 1) {
+ BigDecimal x = this.stack.pop();
+ BigDecimal y = this.stack.pop();
+ BigDecimal r = y.multiply(x);
+ this.stack.push(r);
+ }
+ }
+
+ /**
+ * Takes the top item on the stack, and uses its integer value as the power
+ * for raising the number beneath it.
+ * e.g. before: X Y after: X^Y before: 2 3 after: 8
* @return an error message, or null if there is no error
- */
- public String divide() {
- String result = null;
- if (this.stack.size() > 1) {
- BigDecimal x = this.stack.pop();
- BigDecimal y = this.stack.pop();
- // We use HALF_EVEN rounding because this statistically minimizes
- // cumulative error during repeated calculations.
- try {
- BigDecimal r = y.divide(x, INTERNAL_SCALE,
- RoundingMode.HALF_EVEN);
- this.stack.push(r);
- } catch (ArithmeticException e) {
- result = e.getMessage();
- }
- }
- return result;
- }
-
- /**
- * Computes the reciprocal of the top element on the stack, and replaces it
- * with the result.
- * @return an error message, or null if there is no error
- */
- public String reciprocal() {
- String result = null;
- if (!this.stack.isEmpty()) {
- BigDecimal x = this.stack.pop();
- try {
- BigDecimal y = BigDecimal.ONE.divide(x, INTERNAL_SCALE,
- RoundingMode.HALF_EVEN);
- this.stack.push(y);
- } catch (ArithmeticException e) {
- result = e.getMessage();
- }
- }
- return result;
- }
-
- /**
- * Sets the display scale, in decimal places.
- * Computation is always performed to the INTERNAL_SCALE.
- * @param newscale new scale value
- */
- public void setScale(final int newscale) {
- this.scale = newscale;
- }
-
- /**
- * Sets the display scale to the integer value of the top element on the
- * stack, as long as that value is less than the INTERNAL_SCALE.
- */
- public void setScale() {
- if (!this.stack.isEmpty()) {
- BigDecimal x = this.stack.pop();
- int sc = x.intValue();
- if (sc < INTERNAL_SCALE) {
- setScale(sc);
- }
- }
- }
-
- /**
- * Gets the current display scale.
- * @return
- */
- public int getScale() {
- return this.scale;
- }
-
- /**
- * Computes the square root of the value on the top of the stack, and
- * replaces that value with the result.
- */
- public void sqrt() {
- if (!this.stack.isEmpty()) {
- BigDecimal x = sqrt(this.stack.pop(), INTERNAL_SCALE);
- this.stack.push(x);
- }
- }
-
- /**
- * Computes the square root of x to a given scale, x >= 0.
- * Use Newton's algorithm.
- * Taken from "Java Number Cruncher: The Java Programmer's Guide to
- * Numerical Computing" (Ronald Mak, 2003) http://goo.gl/CXpi2
- * @param x the value of x
- * @param scale the desired scale of the result
- * @return the result value
- */
- private static BigDecimal sqrt(final BigDecimal x, final int scale)
- {
- // Check that x >= 0.
- if (x.signum() < 0) {
- throw new IllegalArgumentException("x < 0");
- }
+ */
+ // Returns error message, or null if no error.
+ public String power() {
+ String result = null;
+ if (this.stack.size() > 1) {
+ BigDecimal y = this.stack.pop();
+ BigDecimal x = this.stack.pop();
+ try {
+ int yi = y.intValueExact();
+ BigDecimal r = x.pow(yi);
+ this.stack.push(r);
+ } catch (ArithmeticException e) {
+ result = "Value out of range";
+ }
+ }
+ return result;
+ }
- // n = x*(10^(2*scale))
- BigInteger n = x.movePointRight(scale << 1).toBigInteger();
+ /**
+ * Uses the top number on the stack to divide the number beneath it.
+ * Replaces both with the result of the division.
+ * e.g. before: x y after: x/y before: 4 2 after: 2
+ * @return an error message, or null if there is no error
+ */
+ public String divide() {
+ String result = null;
+ if (this.stack.size() > 1) {
+ BigDecimal x = this.stack.pop();
+ BigDecimal y = this.stack.pop();
+ // We use HALF_EVEN rounding because this statistically minimizes
+ // cumulative error during repeated calculations.
+ try {
+ BigDecimal r = y.divide(x, INTERNAL_SCALE,
+ RoundingMode.HALF_EVEN);
+ this.stack.push(r);
+ } catch (ArithmeticException e) {
+ result = e.getMessage();
+ }
+ }
+ return result;
+ }
- // The first approximation is the upper half of n.
- int bits = (n.bitLength() + 1) >> 1;
- BigInteger ix = n.shiftRight(bits);
- BigInteger ixPrev;
+ /**
+ * Computes the reciprocal of the top element on the stack, and replaces it
+ * with the result.
+ * @return an error message, or null if there is no error
+ */
+ public String reciprocal() {
+ String result = null;
+ if (!this.stack.isEmpty()) {
+ BigDecimal x = this.stack.pop();
+ try {
+ BigDecimal y = BigDecimal.ONE.divide(x, INTERNAL_SCALE,
+ RoundingMode.HALF_EVEN);
+ this.stack.push(y);
+ } catch (ArithmeticException e) {
+ result = e.getMessage();
+ }
+ }
+ return result;
+ }
- // Loop until the approximations converge
- // (two successive approximations are equal after rounding).
- do {
- ixPrev = ix;
+ /**
+ * Sets the display scale, in decimal places.
+ * Computation is always performed to the INTERNAL_SCALE.
+ * @param newscale new scale value
+ */
+ public void setScale(final int newscale) {
+ this.scale = newscale;
+ }
- // x = (x + n/x)/2
- ix = ix.add(n.divide(ix)).shiftRight(1);
+ /**
+ * Sets the display scale to the integer value of the top element on the
+ * stack, as long as that value is less than the INTERNAL_SCALE.
+ */
+ public void setScale() {
+ if (!this.stack.isEmpty()) {
+ BigDecimal x = this.stack.pop();
+ int sc = x.intValue();
+ if (sc < INTERNAL_SCALE) {
+ setScale(sc);
+ }
+ }
+ }
- Thread.yield();
- } while (ix.compareTo(ixPrev) != 0);
+ /**
+ * Gets the current display scale.
+ * @return
+ */
+ public int getScale() {
+ return this.scale;
+ }
- return new BigDecimal(ix, scale);
+ /**
+ * Computes the square root of the value on the top of the stack, and
+ * replaces that value with the result.
+ */
+ public void sqrt() {
+ if (!this.stack.isEmpty()) {
+ BigDecimal x = sqrt(this.stack.pop(), INTERNAL_SCALE);
+ this.stack.push(x);
}
-
+ }
+
+ /**
+ * Computes the square root of x to a given scale, x >= 0.
+ * Use Newton's algorithm.
+ * Taken from "Java Number Cruncher: The Java Programmer's Guide to
+ * Numerical Computing" (Ronald Mak, 2003) http://goo.gl/CXpi2
+ * @param x the value of x
+ * @param scale the desired scale of the result
+ * @return the result value
+ */
+ private static BigDecimal sqrt(final BigDecimal x, final int scale)
+ {
+ // Check that x >= 0.
+ if (x.signum() < 0) {
+ throw new IllegalArgumentException("x < 0");
+ }
+
+ // n = x*(10^(2*scale))
+ BigInteger n = x.movePointRight(scale << 1).toBigInteger();
+
+ // The first approximation is the upper half of n.
+ int bits = (n.bitLength() + 1) >> 1;
+ BigInteger ix = n.shiftRight(bits);
+ BigInteger ixPrev;
+
+ // Loop until the approximations converge
+ // (two successive approximations are equal after rounding).
+ do {
+ ixPrev = ix;
+
+ // x = (x + n/x)/2
+ ix = ix.add(n.divide(ix)).shiftRight(1);
+
+ Thread.yield();
+ } while (ix.compareTo(ixPrev) != 0);
+
+ return new BigDecimal(ix, scale);
+ }
+
}
View
206 src/com/ath0/rpn/CalculatorStackTest.java
@@ -1,206 +0,0 @@
-import static org.junit.Assert.*;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Random;
-
-import org.junit.Test;
-
-
-public class CalculatorStackTest {
-
- // Decimal places to test.
- // Should be less than CalculatorStack.INTERNAL_PRECISION.
- private final int MAX_TEST_PRECISION = 20;
-
- // Digits of precision to test.
- // Must be greater than MAX_TEST_PRECISION.
- private final int MAX_DIGITS = 40;
-
- // Number of rounds of testing for each numeric operation.
- private final int TEST_ROUNDS = 100;
-
- @Test
- public void testToString() {
- CalculatorStack s = new CalculatorStack();
- s.setInputBuffer("30.00");
- s.enter();
- s.setInputBuffer("1.15");
- s.add();
- s.setScale(4);
- assertEquals("Incorrect padding of decimals", "31.1500", s.toString());
- s.setScale(0);
- assertEquals("Incorrect zero scaling", "31", s.toString());
- }
-
- @Test
- public void testEnterAndDrop() {
- CalculatorStack s = new CalculatorStack();
- s.setInputBuffer("0078704");
- s.enter();
- assertEquals("Incorrect string to decimal on enter", "78704", s.toString());
- s.enter();
- assertEquals("Double enter destroyed value", "78704", s.toString());
- s.append('4');
- s.append('2');
- s.enter();
- assertEquals("Second entered value failed", "42", s.toString());
- s.drop();
- assertEquals("Drop failed", "78704", s.toString());
- s.drop();
- assertEquals("Double drop failed to empty", "0", s.toString());
- s.drop();
- assertEquals("Drop on empty stack failed", "0", s.toString());
- }
-
- @Test
- public void testAppend() {
- CalculatorStack s = new CalculatorStack();
- s.append('1');
- s.append('2');
- s.append('3');
- assertEquals("Incorrect buffer append", "123", s.toString());
- s.append('.');
- s.append('5');
- assertEquals("Incorrect decimals append", "123.5", s.toString());
- s.append('6');
- s.append('7');
- s.append('.');
- s.append('8');
- assertEquals("Incorrect double decimal append", "123.5678", s.toString());
- }
-
- @Test
- public void testBackspace() {
- CalculatorStack s = new CalculatorStack();
- s.backspace();
- assertEquals("Incorrect backspace on new stack", "0", s.toString());
- s.append('2');
- s.backspace();
- assertEquals("Incorrect backspace on 1-char input", "0", s.toString());
- s.backspace();
- s.setInputBuffer("123.567");
- s.backspace();
- assertEquals("Incorrect backspace", "123.56", s.toString());
- s.backspace();
- s.backspace();
- s.backspace();
- s.append('0');
- s.append('9');
- assertEquals("Incorrect backspace over decimal", "12309", s.toString());
- s.append('.');
- s.append('8');
- assertEquals("Incorrect post-decimal-deletion decimal append", "12309.8", s.toString());
- }
-
- @Test
- public void testChs() {
- CalculatorStack s = new CalculatorStack();
- s.chs();
- assertEquals("Incorrect CHS on clean stack", "0", s.toString());
- s.append('0');
- s.append('0');
- s.chs();
- assertEquals("Incorrect CHS on zero", "00", s.toString());
- s.append('3');
- s.chs();
- assertEquals("Incorrect CHS on non-zero with leading zeros", "-003", s.toString());
- s.clear();
- s.append('5');
- s.chs();
- assertEquals("Incorrect CHS on non-zero", "-5", s.toString());
- s.chs();
- assertEquals("Incorrect CHS on negative", "5", s.toString());
- }
-
- private String randomNumber() {
- StringBuilder s = new StringBuilder(MAX_DIGITS);
- Random r = new Random();
- if (r.nextBoolean()) {
- s.append('-');
- }
- int leftdigits = MAX_DIGITS - MAX_TEST_PRECISION;
- for (int i = 0; i < leftdigits; i++) {
- s.append(Integer.toString(r.nextInt(10)));
- }
- s.append('.');
- for (int i = 0; i < MAX_TEST_PRECISION; i++) {
- s.append(Integer.toString(r.nextInt(10)));
- }
- return s.toString();
- }
-
- @Test
- public void testAdd() {
- for (int j = 0; j < TEST_ROUNDS; j++) {
- // Pick a random test display scale
- Double testscale = new Double(MAX_TEST_PRECISION * Math.random());
- int scale = testscale.intValue();
- CalculatorStack s = new CalculatorStack();
- String a = randomNumber();
- String b = randomNumber();
- BigDecimal da = new BigDecimal(a);
- BigDecimal db = new BigDecimal(b);
- BigDecimal dr = da.add(db);
- BigDecimal xv = dr.setScale(scale, RoundingMode.HALF_UP);
- System.out.println(a + " + " + b + " = " + xv.toPlainString()
- + " at scale " + Integer.toString(scale));
- s.setInputBuffer(a);
- s.enter();
- s.setInputBuffer(b);
- s.add();
- s.setScale(scale);
- assertEquals("Incorrect addition", xv.toPlainString(), s.toString());
- }
- }
-
- @Test
- public void testDivide() {
- for (int j = 0; j < TEST_ROUNDS; j++) {
- // Pick a random test display scale
- Double testscale = new Double(MAX_TEST_PRECISION * Math.random());
- int scale = testscale.intValue();
- CalculatorStack s = new CalculatorStack();
- String a = randomNumber();
- String b = randomNumber();
- BigDecimal da = new BigDecimal(a);
- BigDecimal db = new BigDecimal(b);
- BigDecimal dr = da.divide(db, MAX_TEST_PRECISION + 1, RoundingMode.HALF_EVEN);
- BigDecimal xv = dr.setScale(scale, RoundingMode.HALF_UP);
- System.out.println(a + " / " + b + " = " + xv.toPlainString()
- + " at scale " + Integer.toString(scale));
- s.setInputBuffer(a);
- s.enter();
- s.setInputBuffer(b);
- s.divide();
- s.setScale(scale);
- assertEquals("Incorrect division", xv.toPlainString(), s.toString());
- }
- }
-
- @Test
- public void testMultiply() {
- for (int j = 0; j < TEST_ROUNDS; j++) {
- // Pick a random test display scale
- Double testscale = new Double(MAX_TEST_PRECISION * Math.random());
- int scale = testscale.intValue();
- CalculatorStack s = new CalculatorStack();
- String a = randomNumber();
- String b = randomNumber();
- BigDecimal da = new BigDecimal(a);
- BigDecimal db = new BigDecimal(b);
- BigDecimal dr = da.multiply(db);
- BigDecimal xv = dr.setScale(scale, RoundingMode.HALF_UP);
- System.out.println(a + " * " + b + " = " + xv.toPlainString()
- + " at scale " + Integer.toString(scale));
- s.setInputBuffer(a);
- s.enter();
- s.setInputBuffer(b);
- s.multiply();
- s.setScale(scale);
- assertEquals("Incorrect multiplication", xv.toPlainString(), s.toString());
- }
- }
-
-
-}
View
187 src/com/ath0/rpn/InputBuffer.java
@@ -9,100 +9,105 @@
*/
public class InputBuffer implements Serializable {
- /**
- * Object version for serialization.
- */
- private static final long serialVersionUID = 1L;
-
- // A sensible initial capacity that should fit all everyday numbers.
- private static final int INITIAL_CAPACITY = 32;
+ /**
+ * Object version for serialization.
+ */
+ private static final long serialVersionUID = 1L;
+
+ // A sensible initial capacity that should fit all everyday numbers.
+ private static final int INITIAL_CAPACITY = 32;
private final StringBuilder buffer = new StringBuilder(INITIAL_CAPACITY);
- public InputBuffer(final String value) {
- super();
- this.set(value);
- }
-
- public InputBuffer() {
- super();
- }
-
+ public InputBuffer(final String value) {
+ super();
+ this.set(value);
+ }
+
+ public InputBuffer() {
+ super();
+ }
+
/**
- * Appends a given character to the buffer, if the result would be a valid
+ * Appends a given character to the buffer, if the result would be a valid
* real number. If '.' is appended to an empty buffer, a '0' is added first.
- * @param ich a digit or '.'
- */
- public void append(final char ich) {
- switch (ich) {
- case '.':
- if (this.buffer.indexOf(".") == -1) {
- if (this.buffer.length() == 0) {
- this.buffer.append('0');
- }
- this.buffer.append('.');
- }
- break;
- case '0':
- if (!"0".equals(this.buffer)) {
- this.buffer.append('0');
- }
- break;
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- this.buffer.append(ich);
- break;
- default:
- Log.e("append", "Ignoring character '" + ich + "'");
- }
- }
-
- /**
- * Deletes the rightmost character in the buffer
- */
- public void delete() {
- final int len = this.buffer.length();
- if (len > 0) {
- this.buffer.setLength(len - 1);
- }
- }
-
- /**
- * Zaps the buffer to be empty (no value, not even zero)
- */
- public void zap() {
- this.buffer.setLength(0);
- }
-
- /**
- * Returns whether the buffer is empty (no value, not even zero)
- * @return
- */
- public boolean isEmpty() {
- return this.buffer != null && this.buffer.length() == 0;
- }
-
- /**
- * Sets the value of the buffer
- * @param value the value, assumed to be a valid numeric
- */
- final public void set(final String value) {
- this.buffer.setLength(0);
- this.buffer.append(value);
- }
+ * @param ich a digit or '.'
+ */
+ public void append(final char ich) {
+ switch (ich) {
+ case '.':
+ if (this.buffer.indexOf(".") == -1) {
+ if (this.buffer.length() == 0) {
+ this.buffer.append('0');
+ }
+ this.buffer.append('.');
+ }
+ break;
+ case '0':
+ if (!"0".equals(this.buffer)) {
+ this.buffer.append('0');
+ }
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ this.buffer.append(ich);
+ break;
+ default:
+ Log.e("append", "Ignoring character '" + ich + "'");
+ }
+ }
+
+ /**
+ * Deletes the rightmost character in the buffer
+ */
+ public void delete() {
+ final int len = this.buffer.length();
+ if (len > 0) {
+ this.buffer.setLength(len - 1);
+ }
+ }
+
+ /**
+ * Zaps the buffer to be empty (no value, not even zero)
+ */
+ public void zap() {
+ this.buffer.setLength(0);
+ }
+
+ /**
+ * Returns whether the buffer is empty (no value, not even zero)
+ * @return
+ */
+ public boolean isEmpty() {
+ return this.buffer != null && this.buffer.length() == 0;
+ }
+
+ /**
+ * Sets the value of the buffer
+ * @param value the value, assumed to be a valid numeric
+ */
+ final public void set(final String value) {
+ this.buffer.setLength(0);
+ this.buffer.append(value);
+ }
+
+ /**
+ * Gets the value of the buffer
+ * @return the value
+ */
+ public String get() {
+ return this.buffer.toString();
+ }
+
+ @Override
+ public String toString() {
+ return this.get();
+ }
- /**
- * Gets the value of the buffer
- * @return the value
- */
- public String get() {
- return this.buffer.toString();
- }
-
}
View
9 tests/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/RPN"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry kind="output" path="bin/classes"/>
+</classpath>
View
34 tests/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>RPNTest</name>
+ <comment></comment>
+ <projects>
+ <project>RPN</project>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
View
19 tests/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.ath0.rpn.test"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.ath0.rpn" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
View
20 tests/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
View
14 tests/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-15
View
BIN tests/res/drawable-hdpi/ic_launcher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN tests/res/drawable-ldpi/ic_launcher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN tests/res/drawable-mdpi/ic_launcher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN tests/res/drawable-xhdpi/ic_launcher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
6 tests/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">RPNTestTest</string>
+
+</resources>
View
154 tests/src/com/ath0/rpn/test/CalculatorStackTest.java
@@ -0,0 +1,154 @@
+package com.ath0.rpn.test;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Random;
+
+import junit.framework.TestCase;
+import android.util.Log;
+
+import com.ath0.rpn.CalculatorStack;
+
+public class CalculatorStackTest extends TestCase {
+
+ // Decimal places to test.
+ // Should be less than CalculatorStack.INTERNAL_PRECISION.
+ private final int MAX_TEST_PRECISION = 20;
+
+ // Digits of precision to test.
+ // Must be greater than MAX_TEST_PRECISION.
+ private final int MAX_DIGITS = 40;
+
+ // Number of rounds of testing for each numeric operation.
+ private final int TEST_ROUNDS = 100;
+
+ public static void testToString() {
+ CalculatorStack s = new CalculatorStack();
+ s.push("30.00");
+ s.push("1.15");
+ s.add();
+ s.setScale(4);
+ assertEquals("Incorrect padding of decimals", "31.1500", s.toString());
+ s.setScale(0);
+ assertEquals("Incorrect zero scaling", "31", s.toString());
+ }
+
+ public static void testPushAndDrop() {
+ CalculatorStack s = new CalculatorStack();
+ s.setScale(2);
+ s.push("0078704");
+ Log.d("testEnterAndDrop", "stack = " + s.toString());
+ assertEquals("Incorrect string to decimal with leading zeros", "78704.00", s.toString());
+ s.push("42");
+ assertEquals("Second entered value failed", "42.00", s.toString());
+ s.drop();
+ assertEquals("Drop failed", "78704.00", s.toString());
+ s.drop();
+ assertEquals("Double drop failed to empty", "", s.toString());
+ s.drop();
+ assertEquals("Drop on empty stack failed", "", s.toString());
+ }
+
+ public static void testChs() {
+ CalculatorStack s = new CalculatorStack();
+ s.setScale(2);
+ s.chs();
+ assertEquals("Incorrect CHS on clean stack", "", s.toString());
+ s.push("00");
+ s.chs();
+ Log.d("testChs", "stack = " + s.toString());
+ assertEquals("Incorrect CHS on zero", "0.00", s.toString());
+ s.push("003");
+ s.chs();
+ assertEquals("Incorrect CHS on non-zero with leading zeros", "-3.00", s.toString());
+ s.push("5");
+ s.chs();
+ assertEquals("Incorrect CHS on non-zero", "-5.00", s.toString());
+ s.chs();
+ assertEquals("Incorrect CHS on negative", "5.00", s.toString());
+ }
+
+ private String randomNumber() {
+ StringBuilder s = new StringBuilder(this.MAX_DIGITS);
+ Random r = new Random();
+ if (r.nextBoolean()) {
+ s.append('-');
+ }
+ int leftdigits = this.MAX_DIGITS - this.MAX_TEST_PRECISION;
+ for (int i = 0; i < leftdigits; i++) {
+ s.append(Integer.toString(r.nextInt(10)));
+ }
+ s.append('.');
+ for (int i = 0; i < this.MAX_TEST_PRECISION; i++) {
+ s.append(Integer.toString(r.nextInt(10)));
+ }
+ return s.toString();
+ }
+
+ public void testAdd() {
+ for (int j = 0; j < this.TEST_ROUNDS; j++) {
+ // Pick a random test display scale
+ Double testscale = Double.valueOf(this.MAX_TEST_PRECISION * Math.random());
+ int scale = testscale.intValue();
+ CalculatorStack s = new CalculatorStack();
+ String a = randomNumber();
+ String b = randomNumber();
+ BigDecimal da = new BigDecimal(a);
+ BigDecimal db = new BigDecimal(b);
+ BigDecimal dr = da.add(db);
+ BigDecimal xv = dr.setScale(scale, RoundingMode.HALF_UP);
+ Log.d("testAdd", a + " + " + b + " = " + xv.toPlainString()
+ + " at scale " + Integer.toString(scale));
+ s.push(a);
+ s.push(b);
+ s.add();
+ s.setScale(scale);
+ assertEquals("Incorrect addition", xv.toPlainString(), s.toString());
+ }
+ }
+
+ public void testDivide() {
+ for (int j = 0; j < this.TEST_ROUNDS; j++) {
+ // Pick a random test display scale
+ Double testscale = Double.valueOf(this.MAX_TEST_PRECISION * Math.random());
+ int scale = testscale.intValue();
+ CalculatorStack s = new CalculatorStack();
+ String a = randomNumber();
+ String b = randomNumber();
+ BigDecimal da = new BigDecimal(a);
+ BigDecimal db = new BigDecimal(b);
+ BigDecimal dr = da.divide(db, this.MAX_TEST_PRECISION + 1, RoundingMode.HALF_EVEN);
+ BigDecimal xv = dr.setScale(scale, RoundingMode.HALF_UP);
+ Log.d("testDivide", a + " / " + b + " = " + xv.toPlainString()
+ + " at scale " + Integer.toString(scale));
+ s.push(a);
+ s.push(b);
+ s.divide();
+ s.setScale(scale);
+ assertEquals("Incorrect division", xv.toPlainString(), s.toString());
+ }
+ }
+
+ public void testMultiply() {
+ for (int j = 0; j < this.TEST_ROUNDS; j++) {
+ // Pick a random test display scale
+ Double testscale = Double.valueOf(this.MAX_TEST_PRECISION * Math.random());
+ int scale = testscale.intValue();
+ CalculatorStack s = new CalculatorStack();
+ String a = randomNumber();
+ String b = randomNumber();
+ BigDecimal da = new BigDecimal(a);
+ BigDecimal db = new BigDecimal(b);
+ BigDecimal dr = da.multiply(db);
+ BigDecimal xv = dr.setScale(scale, RoundingMode.HALF_UP);
+ Log.d("testMultiply", a + " * " + b + " = " + xv.toPlainString()
+ + " at scale " + Integer.toString(scale));
+ s.push(a);
+ s.push(b);
+ s.multiply();
+ s.setScale(scale);
+ assertEquals("Incorrect multiplication", xv.toPlainString(), s.toString());
+ }
+ }
+
+}
View
47 tests/src/com/ath0/rpn/test/InputBufferTest.java
@@ -0,0 +1,47 @@
+package com.ath0.rpn.test;
+
+import junit.framework.TestCase;
+
+import com.ath0.rpn.InputBuffer;
+
+public class InputBufferTest extends TestCase {
+
+ public static void testAppend() {
+ InputBuffer ib = new InputBuffer();
+ ib.append('1');
+ ib.append('2');
+ ib.append('3');
+ assertEquals("Incorrect buffer append", "123", ib.toString());
+ ib.append('.');
+ ib.append('5');
+ assertEquals("Incorrect decimals append", "123.5", ib.toString());
+ ib.append('6');
+ ib.append('7');
+ ib.append('.');
+ ib.append('8');
+ assertEquals("Incorrect double decimal append", "123.5678", ib.toString());
+ }
+
+ public static void testDelete() {
+ InputBuffer ib = new InputBuffer();
+ ib.delete();
+ assertEquals("Incorrect backspace on new buffer", "", ib.toString());
+ ib.append('2');
+ ib.delete();
+ assertEquals("Incorrect backspace on 1-char input", "", ib.toString());
+ ib.delete();
+ ib.set("123.567");
+ ib.delete();
+ assertEquals("Incorrect backspace", "123.56", ib.toString());
+ ib.delete();
+ ib.delete();
+ ib.delete();
+ ib.append('0');
+ ib.append('9');
+ assertEquals("Incorrect backspace over decimal", "12309", ib.toString());
+ ib.append('.');
+ ib.append('8');
+ assertEquals("Incorrect post-decimal-deletion decimal append", "12309.8", ib.toString());
+ }
+
+}

0 comments on commit ad0277c

Please sign in to comment.
Something went wrong with that request. Please try again.