diff --git a/Makefile b/Makefile index 4392e8c..e3e03c4 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,12 @@ lint: .PHONY: test test: - clojure -A:test:runner + clojure -M:test:runner +.PHONY: uberjar uberjar: - clj -A:uberjar + clj -M:uberjar +.PHONY: deploy +deploy: + clj -M:deploy diff --git a/README.md b/README.md index adf1cc2..8159100 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,15 @@ The result of *actual value* approximation to a given scale provides: * *Favorite Number* expresses some common language names for certain numbers. A `0.25` is a favorite number in that that it has the name - `a quarter`. A full approximation result returns three such approximation data structures for a *given value* which is: -* **smaller** than the *actual value* on the scaled number range. -* **greater** than the *actual value* on the scaled number range. -* **around** the *actual value* on the scaled number range. For this a is chosen from the above two which is closer to the *actual value*. - +* **less** than the *actual value* on the scaled number range. +* **more** than the *actual value* on the scaled number range. +* **around** the *actual value* on the scaled number range. For this 'less' or 'more' value closer to the *actual value* is chosen. + +Lastly the number formatting can be specified: +* **words** - spell out the number in words (110 -> hundred and ten). +* **bites** - spell out the number using bite size style shortening (1022 -> 1k). +* **numbers** - report number as is. + ## Languages Numeric approximation has two functionality points which are language dependent @@ -63,10 +68,12 @@ Currently supported languages: ## Usage -Number Words exposes approximation functionality through `approximations` function which takes on the following parameters: -* `language` - `:de` or `:en` +Number Words exposes approximation functionality through `numeric-expression` function which takes on the following parameters: * `actual-value` - the number to approximate * `scale` - at which the approximation is to be performed. +* `language` - use two letter language code (like :pt), default is :en +* `relation` - what kind of relation to between actual and given value to use (valid values specified in `:numberwords.domain/relation`) +* `formatting` - which number formatting should be used (valid values specified in `:numberwords.domain/formatting`) ### Installation @@ -76,12 +83,12 @@ Number Words exposes approximation functionality through `approximations` functi _Leiningen_ ``` -[ai.tokenmill.numberwords/numberwords "1.0.2"] +[ai.tokenmill.numberwords/numberwords "1.1.0"] ``` _deps.edn_ ``` -ai.tokenmill.numberwords/numberwords {:mvn/version "1.0.2"} +ai.tokenmill.numberwords/numberwords {:mvn/version "1.1.0"} ``` Usage example: @@ -89,23 +96,19 @@ Usage example: ``` (require '[numberwords.core :as nw]) -(nw/approximations :en 0.258 1/4) +(numeric-expression 144 10 :en :numberwords.domain/around :numberwords.domain/words) +=> +"around one hundred forty" + +(numeric-expression 144 10 :de :numberwords.domain/less :numberwords.domain/numbers) => -#:numwords{:around - #:numwords{:hedges #{"approximately" "about" "around"}, - :text "zero point two five", - :given-value 1/4, - :favorite-number #{"a quarter"}}, - :more-than - #:numwords{:hedges #{"over" "more than"}, - :text "zero point two five", - :given-value 1/4, - :favorite-number #{"a quarter"}}, - :less-than - #:numwords{:hedges #{"nearly" "under" "less than"}, - :text "zero point five", - :given-value 1/2, - :favorite-number #{"a half"}}} +"weniger als 150" + +;; with defaults +(numeric-expression 144 10) +=> +"around 140" + ``` ### Java @@ -126,7 +129,7 @@ Or as a _Maven_ dependency ai.tokenmill.numberwords numberwords - 1.0.2-SNAPSHOT + 1.1.0 ``` @@ -136,10 +139,9 @@ Usage example: import ai.tokenmill.numberwords.NumberWords; NumberWords nw = new NumberWords(); -nw.approximations("en", 1.22, 0.1); +nw.numericExpression(1.22, 0.1, "en", "more", "numbers"); ``` - ## Configuration Hedges, favorite numbers can be modified and new languages added via changes to a configuration file - `resources/numwords.edn` diff --git a/deps.edn b/deps.edn index 6c948d7..8b0b53c 100644 --- a/deps.edn +++ b/deps.edn @@ -7,10 +7,10 @@ "-m" "ai.tokenmill.numberwords.NumberWords" "--app-group-id" "ai.tokenmill.numberwords" "--app-artifact-id" "numberwords" - "--app-version" "1.0.2"]} - :deploy {:extra-deps {deps-deploy {:mvn/version "RELEASE"}} + "--app-version" "1.1.0"]} + :deploy {:extra-deps {deps-deploy/deps-deploy {:mvn/version "RELEASE"}} :main-opts ["-m" "deps-deploy.deps-deploy" "deploy" - "target/numberwords-1.0.2.jar"]} + "target/numberwords-1.1.0.jar"]} :test {:extra-paths ["test"] :extra-deps {org.clojure/test.check {:mvn/version "RELEASE"}}} :runner {:extra-deps {com.cognitect/test-runner diff --git a/pom.xml b/pom.xml index f559037..490115f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ numberwords - 1.0.2 + 1.1.0 numberwords @@ -94,13 +94,19 @@ - + + + clojars + Clojars repository + https://clojars.org/repo + + diff --git a/resources/numwords.edn b/resources/numwords.edn index 33b18c3..dd6cb61 100644 --- a/resources/numwords.edn +++ b/resources/numwords.edn @@ -1,48 +1,48 @@ -{:en {:hedges {:equal #{"exactly"} - :around #{"around" "approximately" "about"} - :more #{"more than" "over"} - :less #{"less than" "under" "nearly"}} - :favorite-numbers {1/4 #{"a quarter"} - 1/3 #{"a third"} - 1/2 #{"a half"}}} +{:en {:hedges {:equal ["exactly"] + :around ["around" "approximately" "about"] + :more ["more than" "over"] + :less ["less than" "under" "nearly"]} + :favorite-numbers {1/4 ["a quarter"] + 1/3 ["a third"] + 1/2 ["a half"]}} - :de {:hedges {:equal #{"genau"} - :around #{"ungefähr" "etwa"} - :more #{"mehr als" "über"} - :less #{"weniger als" "unter"}} - :favorite-numbers {1/4 #{"Viertel"} - 1/3 #{"Drittel"} - 1/2 #{"Hälfte"}}} + :de {:hedges {:equal ["genau"] + :around ["ungefähr" "etwa"] + :more ["mehr als" "über"] + :less ["weniger als" "unter"]} + :favorite-numbers {1/4 ["Viertel"] + 1/3 ["Drittel"] + 1/2 ["Hälfte"]}} - :pt {:hedges {:equal #{"exatamente"} - :around #{"cerca de" "aproximadamente"} - :more #{"mais de" "acima de"} - :less #{"menos de" "abaixo de"}} - :favorite-numbers {1/4 #{"um quarto"} - 1/3 #{"um terço"} - 1/2 #{"metade"}}} + :pt {:hedges {:equal ["exatamente"] + :around ["cerca de" "aproximadamente"] + :more ["mais de" "acima de"] + :less ["menos de" "abaixo de"]} + :favorite-numbers {1/4 ["um quarto"] + 1/3 ["um terço"] + 1/2 ["metade"]}} - :lt {:hedges {:equal #{"lygiai"} - :around #{"apie" "apytiksliai"} - :more #{"daugiau nei" "virš"} - :less #{"mažiau nei" "mažiau"}} - :favorite-numbers {1/4 #{"ketvirtis"} - 1/3 #{"trečdalis"} - 1/2 #{"pusė"}}} + :lt {:hedges {:equal ["lygiai"] + :around ["apie" "apytiksliai"] + :more ["daugiau nei" "virš"] + :less ["mažiau nei" "mažiau"]} + :favorite-numbers {1/4 ["ketvirtis"] + 1/3 ["trečdalis"] + 1/2 ["pusė"]}} - :fr {:hedges {:equal #{"exactement"} - :around #{"environ"} - :more #{"plus de"} - :less #{"moins de"}} - :favorite-numbers {1/4 #{"un quart"} - 1/3 #{"un tiers"} - 1/2 #{"un demi"}}} + :fr {:hedges {:equal ["exactement"] + :around ["environ"] + :more ["plus de"] + :less ["moins de"]} + :favorite-numbers {1/4 ["un quart"] + 1/3 ["un tiers"] + 1/2 ["un demi"]}} - :ru {:hedges {:equal #{"ровно"} - :around #{"около" "примерно"} - :more #{"больше чем" "более"} - :less #{"меньше чем" "менее"}} - :favorite-numbers {1/4 #{"четверть"} - 1/3 #{"треть"} - 1/2 #{"пол" "половина"}}} + :ru {:hedges {:equal ["ровно"] + :around ["около" "примерно"] + :more ["больше чем" "более"] + :less ["меньше чем" "менее"]} + :favorite-numbers {1/4 ["четверть"] + 1/3 ["треть"] + 1/2 ["пол" "половина"]}} } diff --git a/src/numberwords/number_ops.clj b/src/numberwords/approx_math.clj similarity index 58% rename from src/numberwords/number_ops.clj rename to src/numberwords/approx_math.clj index 038684c..c22cc13 100644 --- a/src/numberwords/number_ops.clj +++ b/src/numberwords/approx_math.clj @@ -1,4 +1,4 @@ -(ns numberwords.number-ops) +(ns numberwords.approx-math) (defn nat-num? [x] (not (neg? x))) @@ -25,3 +25,17 @@ [(rationalize (- rational-av m)) (rationalize (- (+ rational-av scale) m))])) +(defn distances-from-edges + [actual-value scale] + (let [[start end] (bounding-box actual-value scale)] + [[start (delta start actual-value)] + [end (delta end actual-value)]])) + +(defn unreliable? + "Actual value and scale values which will generate unreliable results: + - actual-value much bigger that the scale + - scale is bigger than the actual value" + [actual-value scale] + ;; 1000x difference between the scale and value is a random choice + (or (< 1000 (/ actual-value scale)) + (and (< 1 scale) (< actual-value scale)))) diff --git a/src/numberwords/config.clj b/src/numberwords/config.clj index ca6f914..47f13b3 100644 --- a/src/numberwords/config.clj +++ b/src/numberwords/config.clj @@ -10,6 +10,10 @@ :favorite-numbers (fn [given-value] (get-in config [:favorite-numbers given-value]))}))) +(defn numwords-config [] + (with-open [r (io/reader (io/resource "numwords.edn"))] + (edn/read (PushbackReader. r)))) + (defn supported-langauges [] (with-open [r (io/reader (io/resource "numwords.edn"))] (-> (PushbackReader. r) diff --git a/src/numberwords/core.clj b/src/numberwords/core.clj index 1954f6c..0714615 100644 --- a/src/numberwords/core.clj +++ b/src/numberwords/core.clj @@ -1,120 +1,113 @@ (ns numberwords.core (:require [clojure.spec.alpha :as s] - [clojure.string :as string] - [numberwords.number-ops :as no] + [numberwords.approx-math :as math] [numberwords.config :as cfg] - [numberwords.text :as text])) + [numberwords.formatting.bitesize :as bitesize] + [numberwords.formatting.text :as text] + [numberwords.domain :as nd])) -;;the value for which numeric expression is to be calculated -(s/def :numwords/actual-value (s/and number? no/nat-num? no/not-inf?)) +(def config (cfg/numwords-config)) -;;textual expression of the number -(s/def :numwords/text (s/and string? #(not (string/blank? %)))) -;;textual hedge describing the relation between given and actual value -(s/def :numwords/hedges (s/coll-of string? :kind set?)) -;;given value - the value given by the numeric expression calculation as the one -;;rounding the actual value -(s/def :numwords/given-value (s/and number? no/nat-num?)) -;;in case there are favorite expressions for a given number, spell it out -(s/def :numwords/favorite-number (s/coll-of string? :kind set?)) +(defn numeric-relations + "Construct numeric relations for the actual value to the numbers on a scale + * actual-value - a number which has to be expressed + * scale - specifies the granularity of the rounding: + 1/10 for one decimal point, 10 for rounding to tenths, and so on." + [actual-value scale] + (let [[[num> delta>] [num< delta<]] (math/distances-from-edges actual-value scale) + closest-num (min num> num<) + equal-to (cond (= delta> 0.0) num> + (= delta< 0.0) num< + :else nil)] + (cond + equal-to {::nd/equal equal-to} + (math/unreliable? actual-value scale) {::nd/around closest-num} + :else {::nd/around closest-num + ::nd/more num> + ::nd/less num<}))) -;;numeric approximation provides all the info describing how an actual -;;value can be described in an approximate manner -(s/def :numwords/num-approximation (s/keys :req [:numwords/text :numwords/hedges :numwords/given-value] - :upt [:numwords/favorite-number])) +(s/fdef numeric-relations + :args (s/cat :actual-value ::nd/actual-value + :scale ::nd/scale) + :ret ::nd/given-value-relations) -;;main resulting data structure with three branches -;; :equal in case actual value is equal to given value -;; :unreliable will be provided when working with huge numbers and where scale is -;; larger than actual value -;; :unequal main case when we will have all three numeric expressions generated -(s/def :numwords/numeric-expressions - (s/or :equal (s/map-of #{:numwords/equal} :numwords/num-approximation) - :unreliable (s/map-of #{:numwords/around} :numwords/num-approximation) - :unequal (s/map-of #{:numwords/around :numwords/more-than :numwords/less-than} - :numwords/num-approximation :min-count 3))) +(defn number->text + "Translate number to text in a given language" + [number language] (text/number->text language number)) -;;rounding (snapping) scale to use when calculating values which will be -;;provided as numeric expressions -(s/def :numwords/scale (s/or :fraction (s/and ratio? #(and (> % 0) - (> (denominator %) - (numerator %)))) - :natural-num (s/and number? pos-int?))) +(s/fdef number->text + :args (s/cat :num ::nd/actual-value :lang ::nd/language) + :ret string?) -;;supported languages -(s/def :numwords/language (cfg/supported-langauges)) +(defn number->bitesize + "Translate number to bite style number formatting" + [number] (bitesize/number->bitesize number)) -(defn distances-from-edges - [actual-value [start end]] - [[start (no/delta start actual-value)] - [end (no/delta end actual-value)]]) +(s/fdef number->bitesize + :args (s/cat :num ::nd/actual-value) + :ret string?) -(defn build-expr - "Build resulting spec conformant numeric expression description" - [hedges fav-numbers text given-value] - (cond-> {:numwords/hedges hedges - :numwords/text text - :numwords/given-value given-value} - fav-numbers (assoc :numwords/favorite-number fav-numbers))) +(defn hedge + "List of words describing the relation between given and actual value" + [relation language] + ;;FIXME how to do simple keyword given namespaced one? + (let [relation-kw (keyword (name relation))] + (-> config language :hedges relation-kw))) -(defn unreliable? - "Actual value and scale values which will generate results which are unreliable: - - actual-value much bigger that the scale - - scale is bigger than the actual value" - [actual-value scale] - ;; 1000x difference between the scale and value - ;; is chosen completely randomly - (or (< 1000 (/ actual-value scale)) - (and (< 1 scale) (< actual-value scale)))) +(s/fdef hedge + :args (s/cat :relation ::nd/relation :lang ::nd/language) + :ret (s/coll-of string? :kind set?)) -(defn approximations - "Numeric approximations translate given numeric value to a set of simplified - approximations of that number. Function parameters: +(defn favorite-number + "List of phrases which can be used instead of the number. Like `a half`" + [value language] (-> config language :favorite-numbers (get value))) - * actual-value - a number which has to be expressed - * language - to be used for text generation - * scale - specifies the granularity of the rounding: - 1/10 for one decimal point, 10 for rounding to tenths, and so on. +(s/fdef favorite-number + :args (s/cat :relation ::nd/given-value :lang ::nd/language) + :ret (s/or :has-fav-nums (s/coll-of string? :kind set?) + :no-fav-nums nil?)) - Resulting approximation provides: +(defn number-with-precision [num scale] + (if (ratio? num) + (double num) + num)) - * given-value - a number which is a closest approximation of the actual value - * relation - how given-value relates to actual value: equal, more, less, or around - * hedges - a list of words describing the relation - * text - given-number translated to text" - [language actual-value scale] - (let [{:keys [hedges favorite-numbers]} - (cfg/numwords-for language) - text (partial text/number->text language) - value-range (no/bounding-box actual-value scale) - [[num> delta>] [num< delta<]] (distances-from-edges actual-value value-range) - closest-num (min num> num<) - equal-to (cond (= delta> 0.0) num> - (= delta< 0.0) num< - :else nil)] - (if equal-to - {:numwords/equal (build-expr (hedges :equal) - (favorite-numbers equal-to) - (text equal-to) - equal-to)} - (let [around (build-expr (hedges :around) - (favorite-numbers closest-num) - (text closest-num) - closest-num)] - (if (unreliable? actual-value scale) - {:numwords/around around} - {:numwords/around around - :numwords/more-than (build-expr (hedges :more) - (favorite-numbers num>) - (text num>) - num>) - :numwords/less-than (build-expr (hedges :less) - (favorite-numbers num<) - (text num<) - num<)}))))) +(defn possible-relation + "Get the relation which is possible in the current given value approximations. + If we have regular case with all three (less,more,equal) detected then return it + else first check if we have 'equal' relation, if this is not present go for 'around'" + [given-val-relations requested-relation] + (let [relation-types (set (keys given-val-relations))] + (or + (get relation-types requested-relation) + (get relation-types ::nd/equal) + (get relation-types ::nd/about)))) + +(defn numeric-expression + ([actual-value scale] + (numeric-expression actual-value scale :en ::nd/around ::nd/bites)) + ([actual-value scale relation formatting] + (numeric-expression actual-value scale :en relation formatting)) + ([actual-value scale language relation formatting] + (let [relations (numeric-relations actual-value scale) + actual-relation (possible-relation relations relation) + given-value (get relations actual-relation) + fav-num (first (favorite-number given-value language))] + (format "%s %s" + (first (hedge actual-relation language)) + (condp = formatting + ::nd/numbers (number-with-precision given-value scale) + ::nd/words (or fav-num (number->text given-value language)) + ::nd/bites + ;;FIXME this part is not good, plus revisit + ;; `number-with-precision` it has to work for `numbers` + (if (or (rational? given-value) + (ratio? given-value) + (< scale 1)) + (number-with-precision given-value scale) + (number->bitesize given-value))))))) -(s/fdef approximations - :args (s/cat :language :numwords/language - :actual-value :numwords/actual-value - :scale :numwords/scale) - :ret :numwords/numeric-expressions) +(s/fdef numeric-expression + :args (s/cat :num ::nd/actual-value :scale ::nd/scale :lang ::nd/language + :relation ::nd/relation :formatting ::nd/formatting) + :ret string?) diff --git a/src/numberwords/domain.clj b/src/numberwords/domain.clj new file mode 100644 index 0000000..94f2c59 --- /dev/null +++ b/src/numberwords/domain.clj @@ -0,0 +1,42 @@ +(ns numberwords.domain + (:require [clojure.spec.alpha :as s] + [numberwords.config :as cfg] + [numberwords.approx-math :as math])) + +;;the value for which numeric expression is to be calculated +(s/def ::actual-value (s/and number? math/nat-num? math/not-inf?)) + +;;textual expression of the number +;; (s/def ::text (s/and string? #(not (string/blank? %)))) +;;textual hedge describing the relation between given and actual value +(s/def ::hedges (s/coll-of string? :kind set?)) +;;given value - the value given by the numeric expression calculation as the one +;;rounding the actual value +(s/def ::given-value (s/and number? math/nat-num?)) +;;in case there are favorite expressions for a given number, spell it out +(s/def ::favorite-number (s/coll-of string? :kind set?)) + +(s/def ::relation #{::around ::more ::less :equal}) + +;;Given value relation to the actual value - a number on a scale grid +;; :equal in case actual value is equal to given value +;; :unreliable will be provided when working with huge numbers and where scale is +;; larger than actual value +;; :unequal main case when we will have all three numeric expressions generated +(s/def ::given-value-relations + (s/or :equal (s/map-of #{::equal} ::given-value) + :unreliable (s/map-of #{::around} ::given-value) + :unequal (s/map-of #{::around ::more ::less} + ::given-value :min-count 3))) + +;;rounding (snapping) scale to use when calculating values which will be +;;provided as numeric expressions +(s/def ::scale (s/or :fraction (s/and ratio? #(and (> % 0) + (> (denominator %) + (numerator %)))) + :natural-num (s/and number? pos-int?))) + +;;supported languages +(s/def ::language (cfg/supported-langauges)) + +(s/def ::formatting #{::words ::bites ::numbers}) diff --git a/src/numberwords/formatting/bitesize.clj b/src/numberwords/formatting/bitesize.clj new file mode 100644 index 0000000..28672a6 --- /dev/null +++ b/src/numberwords/formatting/bitesize.clj @@ -0,0 +1,23 @@ +(ns numberwords.formatting.bitesize + (:require [clojure.spec.alpha :as s])) + +(def sizes '("" "k" "M" "B" "T")) + +(defn invalid? [n] (or (Double/isNaN n) (Double/isInfinite n))) + +(defn number->bitesize + "Algorithm taken from: + https://programming.guide/java/formatting-byte-size-to-human-readable-format.html " + [n] + (if (invalid? n) + {::number n} + (loop [number (Math/abs n) + number-letters sizes] + (cond + (nil? (peek number-letters)) (str n) + (= 0 (quot number 1000)) (str number (peek number-letters)) + :else (recur (quot number 1000) (pop number-letters)))))) + +(s/fdef number->bitesize + :args (s/cat :number number?) + :ret string?) diff --git a/src/numberwords/text.clj b/src/numberwords/formatting/text.clj similarity index 94% rename from src/numberwords/text.clj rename to src/numberwords/formatting/text.clj index 6bf436d..64d1916 100644 --- a/src/numberwords/text.clj +++ b/src/numberwords/formatting/text.clj @@ -1,4 +1,4 @@ -(ns numberwords.text +(ns numberwords.formatting.text (:import com.ibm.icu.text.RuleBasedNumberFormat java.util.Locale)) diff --git a/src/numberwords/java.clj b/src/numberwords/java.clj index 787cfc2..81bb772 100644 --- a/src/numberwords/java.clj +++ b/src/numberwords/java.clj @@ -1,17 +1,41 @@ (ns numberwords.java (:require [clojure.pprint :as pp] - [numberwords.core :as nw])) + [numberwords.core :as nw] + [numberwords.domain :as nd] + [clojure.string :as string])) (gen-class :name ai.tokenmill.numberwords.NumberWords :main true :prefix NW- - :methods [[approximations [String Double Double] java.util.Map]]) + :methods [[numericExpression + [Double Double String String String] + java.lang.String]]) -(defn NW-approximations [_ language actual-value scale] - (nw/approximations (keyword language) actual-value (rationalize scale))) +(def label->relation {"equal" ::nd/equal + "around" ::nd/around + "less" ::nd/less + "more" ::nd/more}) -(defn NW-main [& [language actual-value scale]] +(def label->format {"words" ::nd/words + "bites" ::nd/bites + "numbers" ::nd/numbers}) + +(defn NW-numericExpression [_ actual-value scale language + relation formatting] + (nw/numeric-expression actual-value scale (keyword language) + (get label->relation relation) + (get label->format formatting))) + +(defn NW-main [& [actual-value scale language relation formatting]] (pp/pprint - (NW-approximations nil language - (Double/parseDouble actual-value) - (Double/parseDouble scale)))) + (NW-numericExpression + nil + (if (string/includes? actual-value ".") + (Double/parseDouble actual-value) + (Integer/parseInt actual-value)) + (if (string/includes? scale ".") + (Double/parseDouble scale) + (Integer/parseInt scale)) + language + relation + formatting))) diff --git a/test/numberwords/core_test.clj b/test/numberwords/core_test.clj index f012eab..ec165c0 100644 --- a/test/numberwords/core_test.clj +++ b/test/numberwords/core_test.clj @@ -1,94 +1,41 @@ (ns numberwords.core-test (:require [clojure.spec.test.alpha :as st] - [clojure.spec.alpha :as s] - [clojure.test :refer [is are deftest]] - [clojure.test.check.clojure-test :refer [defspec]] - [clojure.test.check.properties :as prop] - [numberwords.core :refer [approximations] :as nw])) + [clojure.test :refer [deftest are]] + [numberwords.domain :as nd] + [numberwords.core :as nw])) -(st/instrument `approximations) - -(defspec check-approximations 200 - (prop/for-all - [lang (s/gen :numwords/language) - value (s/gen :numwords/actual-value) - scale (s/gen :numwords/scale)] - (let [result (approximations lang value scale)] - (cond - (= [:numwords/around] (keys result)) - ;;this branch is here to handle big numbers where - ;;calcs become unreliable due to floating point operations - (let [gv (get-in result [:numwords/around :numwords/given-value])] - (is (or (< 1000 (/ gv scale)) - (<= (Math/abs (double (- gv value))) - (* 2 (Math/ceil scale)))))) - - (= [:numwords/equal] (keys result)) - (is (= (double value) - (double - (get-in result [:numwords/equal :numwords/given-value])))) - - :else - (is (<= (get-in result [:numwords/more-than :numwords/given-value]) - (rationalize value) - (get-in result [:numwords/less-than :numwords/given-value]))))))) - -(defn given-vals [result] - [(get-in result [:numwords/around :numwords/given-value]) - (get-in result [:numwords/less-than :numwords/given-value]) - (get-in result [:numwords/more-than :numwords/given-value])]) - -(defn fav-nums [result] - [(get-in result [:numwords/around :numwords/favorite-number]) - (get-in result [:numwords/less-than :numwords/favorite-number]) - (get-in result [:numwords/more-than :numwords/favorite-number])]) - -(defn hedges [result] - [(get-in result [:numwords/equal :numwords/hedges]) - (get-in result [:numwords/around :numwords/hedges]) - (get-in result [:numwords/less-than :numwords/hedges]) - (get-in result [:numwords/more-than :numwords/hedges])]) +(-> (st/enumerate-namespace 'numberwords.core) st/check) (deftest hedge-identification - (are [result lang value step] (= result (hedges (approximations lang value step))) - [#{"exactly"} nil nil nil] - :en 0 1/4 - - [nil #{"around" "approximately" "about"} #{"less than" "under" "nearly"} #{"more than" "over"}] - :en 0.54M 1/10 - - [nil #{"apie" "apytiksliai"} #{"mažiau nei" "mažiau"} #{"virš" "daugiau nei"}] - :lt 0.54M 1/10 - - [nil #{"etwa" "ungefähr"} #{"unter" "weniger als"} #{"über" "mehr als"}] - :de 0.54M 1/10 - - [nil #{"cerca de" "aproximadamente"} #{"menos de" "abaixo de"} #{"mais de" "acima de"}] - :pt 0.54M 1/10)) + (are [result relation language] (= result (nw/hedge relation language)) + ["exactly"] ::nd/equal :en + ["around" "approximately" "about"] ::nd/around :en + ["apie" "apytiksliai"] ::nd/around :lt + ["ungefähr" "etwa"] ::nd/around :de + ["cerca de" "aproximadamente"] ::nd/around :pt)) (deftest actual-values-at-extremes - (are [result lang value step] (= result (get-in (approximations lang value step) - [:numwords/equal :numwords/given-value])) - 0 :en 0 1/4 - 1 :en 1 1/100 - 110 :en 110 10)) + (are [result value scale] (= result (nw/numeric-relations value scale)) + {::nd/equal 0} 0 1/4 + {::nd/equal 1} 1 1/100 + {::nd/equal 110} 110 10)) (deftest given-values-at-favorite-numbers - (are [result lang value step] (= result (given-vals (approximations lang value step))) - [1/2 3/5 1/2] :en 0.54M 1/10 - [0 1/4 0] :en 1/10 1/4 - [0 1/4 0] :en 0.1M 1/4) - - (are [result lang value step] (= result (fav-nums (approximations lang value step))) - [#{"a half"} nil #{"a half"}] :en 0.54M 1/10 - [nil #{"a quarter"} nil] :en 1/10 1/4 - [nil #{"a quarter"} nil] :en 0.1M 1/4 - [nil #{"ketvirtis"} nil] :lt 0.1M 1/4 - [nil #{"Viertel"} nil] :de 0.1M 1/4 - [nil #{"um quarto"} nil] :pt 0.1M 1/4)) - -(deftest non-special-given-values - (are [result lang value step] (= result (given-vals (approximations lang value step))) - [3/4 1 3/4] :en 0.8 1/4 - [1/10 1/5 1/10] :en 0.14 1/10 - [50 60 50] :en 54 10)) + (are [result value language] (= result (nw/favorite-number value language)) + ["a half"] 1/2 :en + ["ketvirtis"] 1/4 :lt)) + +(deftest possible-relations-in-prefered-order + (are [result value scale relation] + (= result (-> value + (nw/numeric-relations scale) + (nw/possible-relation relation))) + ::nd/equal 120 10 ::nd/less + ::nd/less 122 10 ::nd/less)) + +(deftest full-expression + (are [result actual-value language scale relation formatting] + (= result + (nw/numeric-expression actual-value language scale relation formatting)) + "around zero point two" 0.23 1/10 :en ::nd/around ::nd/words + "around 0.2" 0.23 1/10 :en ::nd/around ::nd/numbers)) diff --git a/test/numberwords/formatting/bitesize_test.clj b/test/numberwords/formatting/bitesize_test.clj new file mode 100644 index 0000000..4989b6d --- /dev/null +++ b/test/numberwords/formatting/bitesize_test.clj @@ -0,0 +1,9 @@ +(ns numberwords.formatting.bitesize-test + (:require [numberwords.formatting.bitesize :refer [number->bitesize]] + [clojure.test :refer [deftest are]])) + +(deftest bitesize-formatting + (are [results value] (= results (number->bitesize value)) + "1" 1 + "1k" 1999 + "100000000000000000" 100000000000000000)) diff --git a/test/numberwords/text_test.clj b/test/numberwords/formatting/text_test.clj similarity index 60% rename from test/numberwords/text_test.clj rename to test/numberwords/formatting/text_test.clj index 54aeecc..4a6c3b0 100644 --- a/test/numberwords/text_test.clj +++ b/test/numberwords/formatting/text_test.clj @@ -1,5 +1,5 @@ -(ns numberwords.text-test - (:require [numberwords.text :refer [number->text]] +(ns numberwords.formatting.text-test + (:require [numberwords.formatting.text :refer [number->text]] [clojure.test :refer [deftest is]])) (deftest number-conversion