Skip to content

Commit

Permalink
first shot at approximating floats with ratios
Browse files Browse the repository at this point in the history
  • Loading branch information
davidedc committed Oct 11, 2016
1 parent 79514f1 commit 11013a3
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 1 deletion.
1 change: 1 addition & 0 deletions run-tests.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ selftest = ->
test_abs()
test_adj()
test_arg()
test_approxratio()
test_besselj()
test_bessely()
test_ceiling()
Expand Down
1 change: 1 addition & 0 deletions runtime/defs.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ ABS = counter++
ADD = counter++
ADJ = counter++
AND = counter++
APPROXRATIO = counter++
ARCCOS = counter++
ARCCOSH = counter++
ARCSIN = counter++
Expand Down
1 change: 1 addition & 0 deletions runtime/init.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ defn = ->
std_symbol("add", ADD)
std_symbol("adj", ADJ)
std_symbol("and", AND)
std_symbol("approxratio", APPROXRATIO)
std_symbol("arccos", ARCCOS)
std_symbol("arccosh", ARCCOSH)
std_symbol("arcsin", ARCSIN)
Expand Down
2 changes: 1 addition & 1 deletion runtime/zombocom.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ $.exec = exec
$.parse = parse

do ->
builtin_fns = ["abs", "add", "adj", "and", "arccos", "arccosh", "arcsin", "arcsinh", "arctan", "arctanh", "arg", "atomize", "besselj", "bessely", "binding", "binomial", "ceiling", "check", "choose", "circexp", "clear", "clearall", "clearpatterns", "clock", "coeff", "cofactor", "condense", "conj", "contract", "cos", "cosh", "decomp", "defint", "deg", "denominator", "det", "derivative", "dim", "dirac", "display", "divisors", "do", "dot", "draw", "dsolve", "eigen", "eigenval", "eigenvec", "erf", "erfc", "eval", "exp", "expand", "expcos", "expsin", "factor", "factorial", "factorpoly", "filter", "float", "floor", "for", "Gamma", "gcd", "hermite", "hilbert", "imag", "component", "inner", "integral", "inv", "invg", "isinteger", "isprime", "laguerre", "lcm", "leading", "legendre", "log", "mag", "mod", "multiply", "not", "nroots", "number", "numerator", "operator", "or", "outer", "pattern", "patternsinfo", "polar", "power", "prime", "print", "product", "quote", "quotient", "rank", "rationalize", "real", "rect", "roots", "equals", "shape", "sgn", "silentpattern", "simplify", "sin", "sinh", "sqrt", "stop", "subst", "sum", "symbolsinfo", "tan", "tanh", "taylor", "test", "testeq", "testge", "testgt", "testle", "testlt", "transpose", "unit", "zero"]
builtin_fns = ["abs", "add", "adj", "and", "approxratio", "arccos", "arccosh", "arcsin", "arcsinh", "arctan", "arctanh", "arg", "atomize", "besselj", "bessely", "binding", "binomial", "ceiling", "check", "choose", "circexp", "clear", "clearall", "clearpatterns", "clock", "coeff", "cofactor", "condense", "conj", "contract", "cos", "cosh", "decomp", "defint", "deg", "denominator", "det", "derivative", "dim", "dirac", "display", "divisors", "do", "dot", "draw", "dsolve", "eigen", "eigenval", "eigenvec", "erf", "erfc", "eval", "exp", "expand", "expcos", "expsin", "factor", "factorial", "factorpoly", "filter", "float", "floor", "for", "Gamma", "gcd", "hermite", "hilbert", "imag", "component", "inner", "integral", "inv", "invg", "isinteger", "isprime", "laguerre", "lcm", "leading", "legendre", "log", "mag", "mod", "multiply", "not", "nroots", "number", "numerator", "operator", "or", "outer", "pattern", "patternsinfo", "polar", "power", "prime", "print", "product", "quote", "quotient", "rank", "rationalize", "real", "rect", "roots", "equals", "shape", "sgn", "silentpattern", "simplify", "sin", "sinh", "sqrt", "stop", "subst", "sum", "symbolsinfo", "tan", "tanh", "taylor", "test", "testeq", "testge", "testgt", "testle", "testlt", "transpose", "unit", "zero"]

for fn in builtin_fns
$[fn] = exec.bind(this, fn)
101 changes: 101 additions & 0 deletions sources/approxratio.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
###
Guesses a rational for a given float.
###

Eval_approxratio = ->
theArgument = cadr(p1)
push(theArgument)
zzfloat()
supposedlyTheFloat = pop()
if supposedlyTheFloat.k == DOUBLE
theFloat = supposedlyTheFloat.d
splitBeforeAndAfterDot = theFloat.toString().split(".")
if splitBeforeAndAfterDot.length == 2
numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
theRatio = approxratio(theFloat,precision)
push_rational(theRatio[0], theRatio[1])
else
push_integer(theFloat)
return

# we didn't manage, just leave unexpressed
push_symbol(APPROXRATIO)
push(theArgument)
list(2)


# courtesy of Michael Borcherds
# who ported this to JavaScript under MIT licence
# also see
# https://github.com/geogebra/geogebra/blob/master/common/src/main/java/org/geogebra/common/kernel/algos/AlgoFractionText.java
# potential other ways to do this:
# https://rosettacode.org/wiki/Convert_decimal_number_to_rational
# http://www.homeschoolmath.net/teaching/rational_numbers.php
# http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions

approxratio = (decimal, AccuracyFactor) ->
FractionNumerator = undefined
FractionDenominator = undefined
DecimalSign = undefined
Z = undefined
PreviousDenominator = undefined
ScratchValue = undefined
ret = [
0
0
]
if isNaN(decimal)
return ret
# return 0/0
if decimal == Infinity
ret[0] = 1
ret[1] = 0
# 1/0
return ret
if decimal == -Infinity
ret[0] = -1
ret[1] = 0
# -1/0
return ret
if decimal < 0.0
DecimalSign = -1.0
else
DecimalSign = 1.0
decimal = Math.abs(decimal)
if Math.abs(decimal - Math.floor(decimal)) < AccuracyFactor
# handles exact integers including 0
FractionNumerator = decimal * DecimalSign
FractionDenominator = 1.0
ret[0] = FractionNumerator
ret[1] = FractionDenominator
return ret
if decimal < 1.0e-19
# X = 0 already taken care of
FractionNumerator = DecimalSign
FractionDenominator = 9999999999999999999.0
ret[0] = FractionNumerator
ret[1] = FractionDenominator
return ret
if decimal > 1.0e19
FractionNumerator = 9999999999999999999.0 * DecimalSign
FractionDenominator = 1.0
ret[0] = FractionNumerator
ret[1] = FractionDenominator
return ret
Z = decimal
PreviousDenominator = 0.0
FractionDenominator = 1.0
loop
Z = 1.0 / (Z - Math.floor(Z))
ScratchValue = FractionDenominator
FractionDenominator = FractionDenominator * Math.floor(Z) + PreviousDenominator
PreviousDenominator = ScratchValue
FractionNumerator = Math.floor(decimal * FractionDenominator + 0.5)
# Rounding Function
unless Math.abs(decimal - (FractionNumerator / FractionDenominator)) > AccuracyFactor and Z != Math.floor(Z)
break
FractionNumerator = DecimalSign * FractionNumerator
ret[0] = FractionNumerator
ret[1] = FractionDenominator
ret
1 change: 1 addition & 0 deletions sources/eval.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ Eval_cons = ->
when FACTORPOLY then Eval_factorpoly()
when FILTER then Eval_filter()
when FLOATF then Eval_float()
when APPROXRATIO then Eval_approxratio()
when FLOOR then Eval_floor()
when FOR then Eval_for()
# this is invoked only when we
Expand Down
44 changes: 44 additions & 0 deletions tests/approxratio.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
test_approxratio = ->
run_test [
"approxratio(0.9054054)",
"67/74",

"approxratio(0.518518)",
"14/27",

"approxratio(0.3333)",
"1/3",

"approxratio(0.5)",
"1/2",

"approxratio(3.14159)",
"355/113",

"approxratio(a*3.14)",
"approxratio(a*3.14)",

"approxratio(3.14)",
"22/7",

# see http://davidbau.com/archives/2010/03/14/the_mystery_of_355113.html
"approxratio(3.14159)",
"355/113",

"approxratio(-3.14159)",
"-355/113",

"approxratio(0)",
"0",

"approxratio(0.0)",
"0",

"approxratio(2)",
"2",

"approxratio(2.0)",
"2",

]

0 comments on commit 11013a3

Please sign in to comment.