Skip to content

Commit

Permalink
Fix precision error and update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sadellie committed Dec 23, 2024
1 parent f1f5328 commit e555813
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package com.sadellie.unitto.core.data.converter

import android.content.Context
import android.util.Log
import com.sadellie.unitto.core.common.Token
import com.sadellie.unitto.core.common.isEqualTo
import com.sadellie.unitto.core.common.isLessThan
import com.sadellie.unitto.core.common.setMaxScale
Expand Down Expand Up @@ -499,7 +498,7 @@ constructor(

private fun calculateInput(value: String): BigDecimal {
// Calculate expression in first text field
val calculated = Expression(value.ifEmpty { Token.Digit.DIGIT_0 }).calculate()
val calculated = Expression(value).calculate()
return calculated
}

Expand All @@ -509,9 +508,9 @@ constructor(
inchInput: String,
): BigDecimal {
// Calculate expression in first text field
var calculated = Expression(footInput.ifEmpty { Token.Digit.DIGIT_0 }).calculate()
var calculated = Expression(footInput).calculate()

val calculatedInches = Expression(inchInput.ifEmpty { Token.Digit.DIGIT_0 }).calculate()
val calculatedInches = Expression(inchInput).calculate()
// turn inches into feet so that it all comes down to converting from feet only
val inches = getById(UnitID.inch) as BasicUnit.Default
val feet = getById(UnitID.foot) as BasicUnit.Default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class Expression(
private val roundingMode: RoundingMode = RoundingMode.HALF_EVEN,
private val scale: Int = MAX_SCALE,
) {
private val mathContext = MathContext(scale, roundingMode)
// double precision to have enough decimal points when formatting
private val mathContext = MathContext(scale * 2, roundingMode)
private val tokens = input.tokenize()
private var cursorPosition = 0

Expand Down Expand Up @@ -81,6 +82,8 @@ class Expression(

// Expression := [ "-" ] Term { ("+" | "-") Term }
private fun parseExpression(): BigDecimal {
if (tokens.isEmpty()) return BigDecimal.ZERO.setScale(scale)

var expression = parseTerm()

while (peek() in listOf(Token.Operator.PLUS, Token.Operator.MINUS)) {
Expand Down Expand Up @@ -178,32 +181,32 @@ class Expression(

// sin
if (moveIfMatched(Token.Func.SIN)) {
expr = parseFuncParentheses().sin(radianMode, mathContext)
expr = parseFuncParentheses().sin(radianMode, mathContext).rescaleTrig()
}

// cos
if (moveIfMatched(Token.Func.COS)) {
expr = parseFuncParentheses().cos(radianMode, mathContext)
expr = parseFuncParentheses().cos(radianMode, mathContext).rescaleTrig()
}

// tan
if (moveIfMatched(Token.Func.TAN)) {
expr = parseFuncParentheses().tan(radianMode, mathContext)
expr = parseFuncParentheses().tan(radianMode, mathContext).rescaleTrig()
}

// arsin
if (moveIfMatched(Token.Func.ARSIN)) {
expr = parseFuncParentheses().arsin(radianMode, mathContext)
expr = parseFuncParentheses().arsin(radianMode, mathContext).rescaleTrig()
}

// arcos
if (moveIfMatched(Token.Func.ARCOS)) {
expr = parseFuncParentheses().arcos(radianMode, mathContext)
expr = parseFuncParentheses().arcos(radianMode, mathContext).rescaleTrig()
}

// actan
if (moveIfMatched(Token.Func.ACTAN)) {
expr = parseFuncParentheses().artan(radianMode, mathContext)
expr = parseFuncParentheses().artan(radianMode, mathContext).rescaleTrig()
}

// ln
Expand Down Expand Up @@ -245,4 +248,7 @@ class Expression(

return expr
}

// rescale to avoid precision loss when evaluating special cases in trigonometry
private fun BigDecimal.rescaleTrig(): BigDecimal = this.setScale(scale, RoundingMode.HALF_EVEN)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,29 @@ import org.junit.Test

class ExpressionComplexTest {

@Test fun expression1() = assertExpr("94×π×89×cos(0.5)−3!÷9^(2)×√8", "23064.9104578494")
@Test fun expression1() = assertExpr("94×π×89×cos(0.5)−3!÷9^(2)×√8", "23064.9104581024275792240533984667610923103776000000000")

@Test fun expression2() = assertExpr("√(25)×2+10÷2", "15")
@Test fun expression2() = assertExpr("√(25)×2+10÷2", "15.00000000000")

@Test fun expression3() = assertExpr("(3+4)×(5−2)", "21")
@Test fun expression3() = assertExpr("(3+4)×(5−2)", "21.00000000000000000000")

@Test fun expression4() = assertExpr("8÷4+2×3", "8")
@Test fun expression4() = assertExpr("8÷4+2×3", "8.00000000000000000000")

@Test fun expression5() = assertExpr("2^3+4^2−5×6", "-6")
@Test fun expression5() = assertExpr("2^3+4^2−5×6", "-6.00000000000000000000")

@Test fun expression6() = assertExpr("(10−2)^2÷8+3×2", "14")
@Test fun expression6() = assertExpr("(10−2)^2÷8+3×2", "14.00000000000000000000")

@Test fun expression7() = assertExpr("7!÷3!−5!÷2!", "780")

@Test fun expression8() = assertExpr("(2^2+3^3)÷5−√(16)×2", "-1.8")
@Test fun expression8() = assertExpr("(2^2+3^3)÷5−√(16)×2", "-1.80000000000")

@Test fun expression9() = assertExpr("10×log(100)+2^4−3^2", "27")
@Test fun expression9() = assertExpr("10×log(100)+2^4−3^2", "27.0000000000000000000")

@Test fun expression10() = assertExpr("sin(π÷3)×cos(π÷6)+tan(π÷4)−√3", "0.017949192431123")
@Test fun expression10() = assertExpr("sin(π÷3)×cos(π÷6)+tan(π÷4)−√3", "0.01794919245807576094")

@Test fun expression11() = assertExpr("2^6−2^5+2^4−2^3+2^−2^1+2^0", "41.25")
@Test fun expression11() = assertExpr("2^6−2^5+2^4−2^3+2^−2^1+2^0", "41.2500000000000000000")

@Test fun expression12() = assertExpr("2×(3+4)×(5−2)÷6", "7")
@Test fun expression12() = assertExpr("2×(3+4)×(5−2)÷6", "7.0000000000000000000")

@Test fun expression13() = assertExpr("√64÷5", "1.6")
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,31 @@ import org.junit.Test

class ExpressionSimpleTest {

@Test fun expression1() = assertExpr("789", "789")
@Test fun expression0() = assertExpr("", "0.0000000000")

@Test fun expression2() = assertExpr("0.1+0.2", "0.3")
@Test fun expression1() = assertExpr("789", "789.0000000000")

@Test fun expression3() = assertExpr(".1+.2", "0.3")
@Test fun expression2() = assertExpr("0.1+0.2", "0.3000000000")

@Test fun expression4() = assertExpr("789+200", "989")
@Test fun expression3() = assertExpr(".1+.2", "0.3000000000")

@Test fun expression5() = assertExpr("600×7.89", "4734")
@Test fun expression4() = assertExpr("789+200", "989.0000000000")

@Test fun expression6() = assertExpr("600÷7", "85.7142857143")
@Test fun expression5() = assertExpr("600×7.89", "4734.00000000000000000000")

@Test fun expression7() = assertExpr("(200+200)×200", "80000")
@Test fun expression6() = assertExpr("600÷7", "85.714285714285714286")

@Test fun expression8() = assertExpr("99^5", "9509900499")
@Test fun expression7() = assertExpr("(200+200)×200", "80000.00000000000000000000")

@Test fun expression9() = assertExpr("12!", "479001600")
@Test fun expression8() = assertExpr("99^5", "9509900499.0000000000")

@Test fun expression10() = assertExpr("12#5", "2")
@Test fun expression9() = assertExpr("12!", "479001600.0000000000")

@Test fun `125 plus 9 percent`() = assertExpr("125+9%", "136.25")
@Test fun expression10() = assertExpr("12#5", "2.0000000000")

@Test fun expression11() = assertExpr("12×√5", "26.8328157300")
@Test fun `125 plus 9 percent`() = assertExpr("125+9%", "136.250000000000")

@Test fun expression11() = assertExpr("12×√5", "26.83281572999747635680000000000")

@Test fun expression12() = assertExpr("sin(42)", "-0.9165215479")

Expand All @@ -70,21 +72,21 @@ class ExpressionSimpleTest {

@Test fun expression23() = assertExpr("tan⁻¹(.69)", "34.6056755516", radianMode = false)

@Test fun expression24() = assertExpr("ln(.69)", "-0.3710636814")
@Test fun expression24() = assertExpr("ln(.69)", "-0.37106368139083198583")

@Test fun expression25() = assertExpr("log(.69)", "-0.1611509093")
@Test fun expression25() = assertExpr("log(.69)", "-0.16115090926274468384")

@Test fun expression26() = assertExpr("exp(3)", "20.0855369232")
@Test fun expression26() = assertExpr("exp(3)", "20.085536923187667741")

@Test fun expression27() = assertExpr("π", "3.1415926536")
@Test fun expression27() = assertExpr("π", "3.1415926535897932385")

@Test fun expression28() = assertExpr("e", "2.7182818285")
@Test fun expression28() = assertExpr("e", "2.7182818284590452354")

@Test fun expression29() = assertExpr("0!", "1")

@Test fun expression30() = assertExpr("cos(π)", "-1")
@Test fun expression30() = assertExpr("cos(π)", "-1.0000000000")

@Test fun expression31() = assertExpr("sin(2π)", "0", radianMode = true)
@Test fun expression31() = assertExpr("sin(2π)", "0.0000000000", radianMode = true)

@Test fun expression32() = assertExpr("tan(π)", "0", radianMode = true)
@Test fun expression32() = assertExpr("tan(π)", "0.0000000000", radianMode = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import org.junit.Assert.assertThrows
import java.math.BigDecimal
import java.math.RoundingMode

fun assertExpr(expr: String, result: String, radianMode: Boolean = true) =
fun assertExpr(expr: String, result: String, scale: Int = 10, radianMode: Boolean = true) =
assertEquals(
BigDecimal(result).setScale(10, RoundingMode.HALF_EVEN),
Expression(expr, radianMode).calculate().setScale(10, RoundingMode.HALF_EVEN),
BigDecimal(result),
Expression(expr, radianMode, RoundingMode.HALF_EVEN, scale).calculate(),
)

fun <T : Throwable?> assertExprFail(
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
versionCode = "40"
versionCode = "41"
versionName = "Quick Silver"

# https://mvnrepository.com/artifact/androidx.browser/browser
Expand Down

0 comments on commit e555813

Please sign in to comment.