Skip to content

Commit

Permalink
Bug fixes with fractions
Browse files Browse the repository at this point in the history
- Entering negative fractions would calculate incorrectly
- Fraction rounding didn't work correctly
- Updated and added tests for fractions
- When rounding, allow for a precions of 0 (no decimals)
- Created a spreadsheet to help with fraction testing
- Removed the silly '0/1' in the fraction display
  • Loading branch information
frossm committed Oct 23, 2023
1 parent c487777 commit 8762114
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 35 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>org.fross</groupId>
<artifactId>rpncalc</artifactId>
<version>5.0.14</version>
<version>5.1.0</version>
<packaging>jar</packaging>

<name>rpncalc</name>
Expand Down
2 changes: 1 addition & 1 deletion snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/fross/rpncalc/CommandParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/fross/rpncalc/StackCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
52 changes: 36 additions & 16 deletions src/main/java/org/fross/rpncalc/StackConversions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/fross/rpncalc/StackObj.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/org/fross/rpncalc/CommandParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/test/java/org/fross/rpncalc/StackCommandsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 8762114

Please sign in to comment.