Add fraction display mode

Support for showing results as fractions (mixed numbers or improper) and entering mixed numbers.
pull/6056/head
Martin Scott 2022-07-18 01:04:48 +01:00
parent a4b79ffe4f
commit 7c11de885b
3 changed files with 70 additions and 12 deletions

View File

@ -43,6 +43,8 @@
{:number (comp bn/BigNumber #(str/replace % "," ""))
:percent (fn percent [a] (-> a (.dividedBy 100.00)))
:scientific bn/BigNumber
:mixed-number (fn [whole numerator denominator]
(.plus (.dividedBy (bn/BigNumber numerator) denominator) whole))
:negterm (fn neg [a] (-> a (.negated)))
:expr identity
:add (fn add [a b] (-> a (.plus b)))
@ -92,6 +94,13 @@
:mode-sci (fn format [places]
(swap! env assoc :mode "sci" :places places)
(get @env "last"))
:mode-frac (fn format [max-denominator]
(swap! env dissoc :mode :improper)
(swap! env assoc :mode "frac" :max-denominator max-denominator)
(get @env "last"))
:mode-frac-i (fn format [max-denominator]
(swap! env assoc :mode "frac" :max-denominator max-denominator :improper true)
(get @env "last"))
:mode-norm (fn format [precision]
(swap! env dissoc :mode :places)
(swap! env assoc :precision precision)
@ -141,6 +150,20 @@
(and (< (.-e num) digits)
(.isInteger (.shiftedBy num (+ tolerance digits)))))
(defn format-fraction [numerator denominator improper]
(let [whole (.dividedToIntegerBy numerator denominator)]
(if (or improper (.isZero whole))
(str numerator "/" denominator )
(str whole "_"
(.abs (.modulo numerator denominator)) "/" denominator))))
(defn format-normal [env val]
(let [precision (or (get @env :precision) 21)
display-val (.precision val precision)]
(if (can-fit? display-val precision 1)
(.toFixed display-val)
(.toExponential display-val))))
(defn format-val [env val]
(if (instance? bn/BigNumber val)
(let [mode (get @env :mode)
@ -160,13 +183,19 @@
(.toExponential val places))
(= mode "sci")
(.toExponential val places)
(= mode "frac")
(let [max-denominator (or (get @env :max-denominator) 4095)
improper (get @env :improper)
[numerator denominator] (.toFraction val max-denominator)
delta (.minus (.dividedBy numerator denominator) val)]
(if (or (.isZero delta) (< (.-e delta) -16))
(if (> denominator 1)
(format-fraction numerator denominator improper)
(format-normal env numerator))
(format-normal env val)))
:else
(let [precision (or (get @env :precision) 21)
display_val (.precision val precision)]
(if (can-fit? display_val precision 1)
(.toFixed display_val)
(.toExponential display_val)))))
(format-normal env val)))
val))
(defn eval-lines [s]

View File

@ -23,8 +23,8 @@ tan = <#'\s*'> <'tan('> expr <')'> <#'\s*'>
atan = <#'\s*'> <'atan('> expr <')'> <#'\s*'>
acos = <#'\s*'> <'acos('> expr <')'> <#'\s*'>
asin = <#'\s*'> <'asin('> expr <')'> <#'\s*'>
<posterm> = function | percent | scientific | number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
negterm = <#'\s*'> <'-'> posterm | <#'\s*'> <'-'> pow | <#'\s*'> <'-'> factorial
<posterm> = function | percent | scientific | number | mixed-number | variable | <#'\s*'> <'('> expr <')'> <#'\s*'>
negterm = <#'\s*'> <'-'> ( posterm | pow | factorial )
<term> = negterm | posterm
scientific = #'\s*[0-9]*\.?[0-9]+(e|E)[\-\+]?[0-9]+()\s*'
number = decimal-number | hexadecimal-number | octal-number | binary-number
@ -32,14 +32,17 @@ number = decimal-number | hexadecimal-number | octal-number | binary-number
<hexadecimal-number> = #'\s*0x([0-9a-fA-F]+(,[0-9a-fA-F]+)*(\.[0-9a-fA-F]*)?|[0-9a-fA-F]*\.[0-9a-fA-F]+)\s*'
<octal-number> = #'\s*0o([0-7]+(,[0-7]+)*(\.[0-7]*)?|[0-7]*\.[0-7]+)\s*'
<binary-number> = #'\s*0b([01]+(,[01]+)*(\.[01]*)?|[01]*\.[01]+)\s*'
mixed-number = <#'\s*'> digits <'_'> digits <#'[/_]'> digits <#'\s*'>
percent = number <'%'> <#'\s*'>
variable = #'\s*_*[a-zA-Z]+[_a-zA-Z0-9]*\s*'
toassign = #'\s*_*[a-zA-Z]+[_a-zA-Z0-9]*\s*'
assignment = toassign <#'\s*'> <'='> <#'\s*'> expr
<mode> = <#'\s*\:'> ( mode-fix | mode-sci | mode-norm | mode-base ) <#'\s*'> [comment]
<mode> = <#'\s*\:'> ( mode-fix | mode-sci | mode-norm | mode-frac | mode-frac-i | mode-base ) <#'\s*'> [comment]
mode-fix = <#'(?i)fix(ed)?\s*'> digits
mode-sci = <#'(?i)sci(entific)?\s*'> [digits]
mode-norm = <#'(?i)norm(al)?\s*'> [digits]
mode-frac = <#'(?i)frac(tions?)?\s*'> [digits]
mode-frac-i = <#'(?i)frac(tions?)?-i(mp(roper)?)?\s*'> [digits]
mode-base = mode-hex | mode-dec | mode-oct | mode-bin
<mode-hex> = #'(?i)hex' <#'(?i)(adecimal)?'>
<mode-dec> = #'(?i)dec' <#'(?i)(imal)?'>

View File

@ -206,8 +206,8 @@
[nil "3"] [":norm 1" "E"]
[nil "0.000123"] [":norm 5" "0.000123"]
[nil "1.23e-4"] [":norm 4" "0.000123"]
[nil "123400000"] [":norm 9" "1.234e8"]
[nil "1.234e+8"] [":norm 8" "1.234e8"]))
[nil "123400000"] [":normal 9" "1.234e8"]
[nil "1.234e+8"] [":normal 8" "1.234e8"]))
(testing "display fixed"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "0.123450"] [":fix 6" "0.12345"]
@ -215,12 +215,38 @@
[nil "2.7183"] [":fixed 4" "E"]
[nil "0.001"] [":fix 3" "0.0005"]
[nil "4.000e-4"] [":fix 3" "0.0004"]
[nil "1.00e+21"] [":fix 2" "1e21+0.1"]))
[nil "1.00e+21"] [":fixed 2" "1e21+0.1"]))
(testing "display scientific"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "1e+6"] [":sci" "1e6"]
[nil "3.142e+0"] [":sci 3" "PI"]
[nil "3.14e+2"] [":sci" "3.14*10^2"])))
[nil "3.14e+2"] [":scientific" "3.14*10^2"])))
(deftest fractions
(testing "mixed numbers"
(are [value expr] (= value (run expr))
0 "0_0_1"
1 "0_1/1"
1 "1_0/1"
2.5 "2_1/2"
2.5 "2_1_2"
-4.28 "-4_7/25"
2.00101 "2_101/100000"
-99.2 "-99_8_40"))
(testing "display fractions"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "4_3/8"] [":frac" "4.375"]
[nil "-7_1/4"] [":fraction" "-7.25"]
[nil "2"] [":fractions" "19/20 + 1_1/20"]
[nil "-2"] [":frac" "19/17 - 3_2/17"]
[nil "3.14157"] [":frac" "3.14157"]
[nil "3_14157/100000"] [":frac 100000" "3.14157"]))
(testing "display improper fractions"
(are [values exprs] (= values (calc/eval-lines (str/join "\n" exprs)))
[nil "35/8"] [":frac-i" "4.375"]
[nil "-29/4"] [":frac-imp" "-7.25"]
[nil "3.14157"] [":fractions-improper" "3.14157" ]
[nil "314157/100000"] [":frac-i 100000" "3.14157"])))
(deftest base-conversion
(testing "mixed base input"