diff --git a/pom.xml b/pom.xml
index c82350f..cc4c0bc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
org.fross
rpncalc
- 5.0.14
+ 5.1.0
jar
rpncalc
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index a7ff268..be880e4 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,5 +1,5 @@
name: rpncalc
-version: '5.0.14'
+version: '5.1.0'
summary: The command line Reverse Polish Notation (RPN) calculator
description: |
RPNCalc is an easy to use command line based Reverse Polish
diff --git a/src/main/java/org/fross/rpncalc/CommandParser.java b/src/main/java/org/fross/rpncalc/CommandParser.java
index e0b7543..9e8afdd 100644
--- a/src/main/java/org/fross/rpncalc/CommandParser.java
+++ b/src/main/java/org/fross/rpncalc/CommandParser.java
@@ -206,6 +206,7 @@ public static void Parse(StackObj calcStack, StackObj calcStack2, String cmdInpu
case "frac":
case "fraction":
String[] outString = StackConversions.cmdFraction(calcStack, cmdInputParam);
+ // If there wasn't an error (which would return an empty string), display the results
if (!outString[0].isEmpty()) {
Output.printColorln(Ansi.Color.YELLOW, outString[0]);
Output.printColorln(Ansi.Color.WHITE, outString[1]);
@@ -450,10 +451,17 @@ public static void Parse(StackObj calcStack, StackObj calcStack2, String cmdInpu
BigDecimal fracTop = new BigDecimal(cmdInputParam.substring(0, cmdInputParam.indexOf('/')));
BigDecimal fracBottom = new BigDecimal(cmdInputParam.substring(cmdInputParam.indexOf('/') + 1));
+ Output.debugPrintln("Fraction Top:\t"+fracTop);
+ Output.debugPrintln("Fraction Bot:\t"+fracBottom);
+
// Divide the fraction and get a decimal equivalent
fracDecimalEquiv = fracTop.divide(fracBottom, MathContext.DECIMAL128);
// Overall decimal equivalent (integer + decimal)
+ // If integer is negative, make the decimal negative so we can add them
+ if (fracInteger.signum() < 0) {
+ fracDecimalEquiv = fracDecimalEquiv.multiply(new BigDecimal("-1"));
+ }
BigDecimal endResult = fracInteger.add(fracDecimalEquiv);
// Simply convert the fraction to a decimal and add it to the stack
diff --git a/src/main/java/org/fross/rpncalc/StackCommands.java b/src/main/java/org/fross/rpncalc/StackCommands.java
index 43d49d8..f8e328d 100644
--- a/src/main/java/org/fross/rpncalc/StackCommands.java
+++ b/src/main/java/org/fross/rpncalc/StackCommands.java
@@ -739,8 +739,8 @@ public static void cmdRound(StackObj calcStack, String arg) {
try {
decimalPlaces = Integer.parseInt(arg);
// Ensure a negative number is not provided for decimal points to round
- if (decimalPlaces <= 0) {
- Output.printColorln(Ansi.Color.RED, "ERROR: '" + arg + "' number of decimal places must be > 0");
+ if (decimalPlaces < 0) {
+ Output.printColorln(Ansi.Color.RED, "ERROR: '" + arg + "' number of decimal places must be >= 0");
return;
}
diff --git a/src/main/java/org/fross/rpncalc/StackConversions.java b/src/main/java/org/fross/rpncalc/StackConversions.java
index e29d9c1..c74cb40 100644
--- a/src/main/java/org/fross/rpncalc/StackConversions.java
+++ b/src/main/java/org/fross/rpncalc/StackConversions.java
@@ -94,7 +94,7 @@ public static String[] cmdFraction(StackObj calcStack, String param) {
BigDecimal startingNumber = calcStack.peek();
// If starting number is negative, set a variable then remove the negative sign
- if (startingNumber.compareTo(BigDecimal.ZERO) < 0) {
+ if (startingNumber.signum() < 0) {
negativeNumber = true;
startingNumber = startingNumber.abs();
}
@@ -111,16 +111,35 @@ public static String[] cmdFraction(StackObj calcStack, String param) {
return outputString;
}
+ // Round the decimal number to the right denominator
+ // IF MY_DIM != ((FLOOR((MY_DIM * 16) + 0.5)) / 16)
+ // Reference: https://community.ptc.com/t5/3D-Part-Assembly-Design/Rounding-decimals-to-the-nearest-fraction/td-p/657420
+ BigInteger roundedNumberTemp = BigInteger.ZERO;
+ BigDecimal roundedNumber = BigDecimal.ZERO;
+ try {
+ roundedNumberTemp = startingNumber.multiply(new BigDecimal(denominator)).add(new BigDecimal("0.5")).toBigInteger();
+ roundedNumber = new BigDecimal(roundedNumberTemp).divide(new BigDecimal(denominator));
+ } catch (ArithmeticException ex) {
+ // Ignore error where division doesn't equate to the exact value - we're estimating here...
+ }
+
// Determine the integer portion of the number
- BigInteger integerPart = startingNumber.toBigInteger();
+ BigInteger integerPart = roundedNumber.toBigInteger();
- // Determine the fractional portion as an double
- BigDecimal decimalPart = startingNumber.subtract(new BigDecimal(integerPart));
+ // Need to make the decimal a fraction my multiplying by the 10ths.
+ // Determine the number of decimals places and multiply by 10
+ int scaleMultiplier = 10;
+ for (int i = 1; i < roundedNumber.scale(); i++) {
+ scaleMultiplier *= 10;
+ }
- // Convert to a fraction with provided base
- // This will round to the nearest integer by adding 1/2 to the number and getting it's integer value
- BigDecimal numeratorNotRounded = decimalPart.multiply(new BigDecimal(denominator));
- BigInteger numerator = numeratorNotRounded.add(new BigDecimal(".5")).toBigInteger();
+ // Numerator = decimal portion * scale so we have an integer
+ // Decimal = the scale.
+ // Example: 0.25 becomes 25/100 | 0.123 becomes 123/1000
+ BigDecimal numeratorTemp = roundedNumber.subtract(new BigDecimal(integerPart));
+ numeratorTemp = numeratorTemp.multiply(BigDecimal.valueOf(scaleMultiplier));
+ BigInteger numerator = numeratorTemp.toBigInteger();
+ denominator = BigInteger.valueOf(scaleMultiplier);
// Get the Greatest Common Divisor so we can simply the fraction
BigInteger gcd = Math.GreatestCommonDivisor(numerator, denominator);
@@ -138,16 +157,17 @@ public static String[] cmdFraction(StackObj calcStack, String param) {
// Output the fractional display
// If there is no fractional result, remove it so we don't see '0/1'
- String stackHeader = "-Fraction (Granularity: 1/" + (denominator.multiply(gcd)) + ")";
+ String stackHeader = "-Fraction (Granularity: 1/" + (param) + ")";
+ String result = integerPart + " " + ((numerator.compareTo(BigInteger.ZERO) == 0) ? "" : numerator.toString() + "/" + denominator);
+
+ // Header Top
outputString[0] = "\n" + stackHeader + "-".repeat(Main.configProgramWidth - stackHeader.length());
- if (numerator.compareTo(BigInteger.ZERO) != 0) {
- outputString[1] = " " + calcStack.peek().setScale(8, RoundingMode.HALF_UP) + " is approximately '" + integerPart + " " + numerator + "/"
- + denominator + "'";
- } else {
- outputString[1] = " " + calcStack.peek() + " does not have a fractional component with a base of " + (denominator.multiply(gcd));
- }
+ // Results
+ outputString[1] = " " + calcStack.peek().setScale(5, RoundingMode.HALF_UP) + " is approximately '" + result.trim() + "'";
+ // Header Bottom
outputString[2] = "-".repeat(Main.configProgramWidth) + "\n";
- outputString[3] = integerPart + " " + numerator + "/" + denominator;
+ // Results for testing
+ outputString[3] = result.trim();
return outputString;
}
diff --git a/src/main/java/org/fross/rpncalc/StackObj.java b/src/main/java/org/fross/rpncalc/StackObj.java
index f2267c1..d388873 100644
--- a/src/main/java/org/fross/rpncalc/StackObj.java
+++ b/src/main/java/org/fross/rpncalc/StackObj.java
@@ -152,7 +152,11 @@ public void push(BigDecimal item) {
* @param item
*/
public void push(String item) {
- calcStack.push(new BigDecimal(item, this.mc));
+ try {
+ this.calcStack.push(new BigDecimal(item, this.mc));
+ } catch (Exception ex) {
+ Output.printColorln(Ansi.Color.RED, "Error: " + ex.getMessage());
+ }
}
/**
diff --git a/src/test/java/org/fross/rpncalc/CommandParserTest.java b/src/test/java/org/fross/rpncalc/CommandParserTest.java
index 9caa0c1..98b6c89 100644
--- a/src/test/java/org/fross/rpncalc/CommandParserTest.java
+++ b/src/test/java/org/fross/rpncalc/CommandParserTest.java
@@ -102,7 +102,7 @@ void testFractionInput() {
CommandParser.Parse(stk1, stk2, "-4 1/64", "-4", "1/64");
StackCommands.cmdRound(stk1, "4");
- assertEquals(-3.9844, stk1.peek().doubleValue());
+ assertEquals(-4.0156, stk1.peek().doubleValue());
assertEquals(4, stk1.size());
// Test scientific notation
diff --git a/src/test/java/org/fross/rpncalc/StackCommandsTest.java b/src/test/java/org/fross/rpncalc/StackCommandsTest.java
index 1c27b5d..efaad3d 100644
--- a/src/test/java/org/fross/rpncalc/StackCommandsTest.java
+++ b/src/test/java/org/fross/rpncalc/StackCommandsTest.java
@@ -1068,6 +1068,10 @@ void testCmdRound() {
stk.push(-65.4329);
StackCommands.cmdRound(stk, "12");
assertEquals(-65.4329, stk.pop().doubleValue());
+
+ stk.push(0.1);
+ StackCommands.cmdRound(stk, "0");
+ assertEquals(0, stk.pop().doubleValue());
// Scientific Notation
stk.push(1.23456789e19);
diff --git a/src/test/java/org/fross/rpncalc/StackConversionsTest.java b/src/test/java/org/fross/rpncalc/StackConversionsTest.java
index a0be65a..4ddd5ed 100644
--- a/src/test/java/org/fross/rpncalc/StackConversionsTest.java
+++ b/src/test/java/org/fross/rpncalc/StackConversionsTest.java
@@ -122,34 +122,226 @@ void testCmdDeg2Rad() {
void testCmdFraction() {
StackObj stk = new StackObj();
- // Test positive numbers with different bases
+ // Test #1
+ stk.clear();
stk.push(71.046875);
assertEquals(71.046875, stk.peek().doubleValue());
-
- String[] result = StackConversions.cmdFraction(stk, "");
+ // 64
+ String[] result = StackConversions.cmdFraction(stk, "64");
assertEquals("71 3/64", result[3]);
-
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("71 1/16", result[3]);
+ // 16
result = StackConversions.cmdFraction(stk, "16");
assertEquals("71 1/16", result[3]);
-
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("71", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("71", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("71", result[3]);
+
+ // Test #2
+ stk.clear();
+ stk.push(4.99);
+ assertEquals(4.99, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("4 63/64", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("5", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("5", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("5", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("5", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("5", result[3]);
+
+ // Test #3
+ stk.clear();
+ stk.push(23.212121);
+ assertEquals(23.212121, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("23 7/32", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("23 7/32", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("23 3/16", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("23 1/4", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("23 1/4", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("23", result[3]);
+
+ // Test #3
+ stk.clear();
+ stk.push(71.7272);
+ assertEquals(71.7272, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("71 47/64", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("71 23/32", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("71 3/4", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("71 3/4", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("71 3/4", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("71 1/2", result[3]);
+
+ // Test with negative numbers
+ // Test #-1
+ stk.clear();
+ stk.push(-71.046875);
+ assertEquals(-71.046875, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("-71 3/64", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("-71 1/16", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("-71 1/16", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("-71", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("-71", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("-71", result[3]);
+
+ // Test #-2
+ stk.clear();
+ stk.push(-4.99);
+ assertEquals(-4.99, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("-4 63/64", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("-5", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("-5", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("-5", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("-5", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("-5", result[3]);
+
+ // Test #3
+ stk.clear();
+ stk.push(-23.212121);
+ assertEquals(-23.212121, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("-23 7/32", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("-23 7/32", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("-23 3/16", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("-23 1/4", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("-23 1/4", result[3]);
+ // 2
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("-23", result[3]);
+
+ // Test #3
+ stk.clear();
+ stk.push(-71.7272);
+ assertEquals(-71.7272, stk.peek().doubleValue());
+ // 64
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("-71 47/64", result[3]);
+ // 32
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("-71 23/32", result[3]);
+ // 16
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("-71 3/4", result[3]);
+ // 8
+ result = StackConversions.cmdFraction(stk, "8");
+ assertEquals("-71 3/4", result[3]);
+ // 4
+ result = StackConversions.cmdFraction(stk, "4");
+ assertEquals("-71 3/4", result[3]);
+ // 2
result = StackConversions.cmdFraction(stk, "2");
- assertEquals("71 0/1", result[3]);
+ assertEquals("-71 1/2", result[3]);
+
+
+ // Misc
+ stk.clear();
+ stk.push(71.1);
+ result = StackConversions.cmdFraction(stk, "16");
+ assertEquals("71 1/8", result[3]);
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("71", result[3]);
+
+ stk.clear();
+ stk.push(14.77);
+ result = StackConversions.cmdFraction(stk, "32");
+ assertEquals("14 25/32", result[3]);
// Test negative numbers with different bases
+ stk.clear();
stk.push(-123.456);
- assertEquals(-123.456, stk.peek().doubleValue());
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("-123 1/2", result[3]);
+ stk.push(-123.456);
result = StackConversions.cmdFraction(stk, "");
assertEquals("-123 29/64", result[3]);
- result = StackConversions.cmdFraction(stk, "44");
- assertEquals("-123 5/11", result[3]);
-
- result = StackConversions.cmdFraction(stk, "32");
- assertEquals("-123 15/32", result[3]);
+ // -41 33/34
+ stk.push(-41.970588235);
+ result = StackConversions.cmdFraction(stk, "2");
+ assertEquals("-42", result[3]);
+ // -12.123123123123123
+ stk.push(-12.123123123123123);
+ result = StackConversions.cmdFraction(stk, "64");
+ assertEquals("-12 1/8", result[3]);
result = StackConversions.cmdFraction(stk, "8");
- assertEquals("-123 1/2", result[3]);
+ assertEquals("-12 1/8", result[3]);
stk.clear();
stk.push(1.2e12 / 2.2e14);
diff --git a/tools/FractionTestResults.xlsx b/tools/FractionTestResults.xlsx
new file mode 100644
index 0000000..ebf9bba
Binary files /dev/null and b/tools/FractionTestResults.xlsx differ