Ver código fonte

Add fraction display mode

Support for showing results as fractions (mixed numbers or improper) and entering mixed numbers.
Martin Scott 3 anos atrás
pai
commit
7c11de885b

+ 34 - 5
src/main/frontend/extensions/calc.cljc

@@ -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]

+ 6 - 3
src/main/grammar/calc.bnf

@@ -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)?'>

+ 30 - 4
src/test/frontend/extensions/calc_test.cljc

@@ -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"