# Specification: numbers & arithmetic. (use ../support/harness) (defspec "numbers / arithmetic" ["add" "6" "(+ 1 2 3)"] ["add zero args" "0" "(+)"] ["subtract" "5" "(- 10 3 2)"] ["negate" "-5" "(- 5)"] ["multiply" "24" "(* 2 3 4)"] ["multiply zero args" "1" "(*)"] ["divide" "2" "(/ 10 5)"] ["divide to fraction" "0.5" "(/ 1 2)"] ["inc" "6" "(inc 5)"] ["dec" "4" "(dec 5)"] ["quot" "3" "(quot 10 3)"] ["rem" "1" "(rem 10 3)"] ["mod" "2" "(mod -1 3)"] ["rem negative" "-1" "(rem -1 3)"] ["max" "9" "(max 3 9 1)"] ["min" "1" "(min 3 9 1)"] ["abs" "5" "(abs -5)"] ["promoting + alias" "3" "(+' 1 2)"] ["inc' alias" "6" "(inc' 5)"]) (defspec "numbers / comparison" ["less than" "true" "(< 1 2 3)"] ["less than false" "false" "(< 1 3 2)"] ["greater than" "true" "(> 3 2 1)"] ["<=" "true" "(<= 1 1 2)"] [">=" "true" "(>= 3 3 2)"] ["= numbers" "true" "(= 2 2)"] ["= different" "false" "(= 2 3)"] ["== numeric" "true" "(== 2 2)"] ["not=" "true" "(not= 1 2)"] ["compare less" "-1" "(compare 1 2)"] ["compare equal" "0" "(compare 1 1)"] ["compare greater" "1" "(compare 2 1)"]) (defspec "numbers / predicates" ["zero?" "true" "(zero? 0)"] ["pos?" "true" "(pos? 5)"] ["neg?" "true" "(neg? -5)"] ["even?" "true" "(even? 4)"] ["odd?" "true" "(odd? 3)"] ["number?" "true" "(number? 5)"] ["number? false" "false" "(number? :a)"] ["int?" "true" "(int? 5)"] ["pos-int?" "true" "(pos-int? 5)"] ["neg-int?" "true" "(neg-int? -5)"] ["nat-int? zero" "true" "(nat-int? 0)"] ["nat-int? neg" "false" "(nat-int? -1)"] ["ratio? false" "false" "(ratio? 5)"]) # Symbolic float values and float/double predicates. NOTE: Janet represents # integers and integer-valued doubles identically, so (float? 1.0) is false # (1.0 is indistinguishable from 1) — a documented divergence. Fractional and # non-finite values ARE recognized as floats. (defspec "numbers / floats & symbolic values" ["read ##Inf" "true" "(= ##Inf (/ 1.0 0.0))"] ["read ##-Inf" "true" "(< ##-Inf 0)"] ["##NaN not= itself" "true" "(not (== ##NaN ##NaN))"] ["float? fractional" "true" "(float? 1.5)"] ["double? fractional" "true" "(double? 0.25)"] ["float? integer" "false" "(float? 3)"] ["float? ##Inf" "true" "(float? ##Inf)"] ["double? ##NaN" "true" "(double? ##NaN)"] ["infinite? ##Inf" "true" "(infinite? ##Inf)"] ["infinite? ##-Inf" "true" "(infinite? ##-Inf)"] ["infinite? finite" "false" "(infinite? 1.5)"] ["NaN? ##NaN" "true" "(NaN? ##NaN)"] ["NaN? number" "false" "(NaN? 1.0)"] ["int? ##Inf false" "false" "(int? ##Inf)"] ["pos-int? ##Inf" "false" "(pos-int? ##Inf)"]) # Numeric literal syntaxes. Jolt has no true bignum/ratio/bigdec types, so the # N (bigint) and M (bigdec) suffixes read as the plain number, ratios as the # double quotient; radix integers (NrDDD) are parsed by base. (defspec "numbers / literal syntax" ["bigint suffix N" "42" "42N"] ["bigint zero" "0" "0N"] ["bigdec suffix M" "1.5" "1.5M"] ["bigdec int M" "0" "0.0M"] ["ratio -> double" "0.5" "1/2"] ["ratio 3/4" "0.75" "3/4"] ["neg ratio" "-0.5" "-1/2"] ["radix binary" "10" "2r1010"] ["radix hex-ish" "255" "16rFF"] ["radix base36" "35" "36rZ"] ["hex" "255" "0xFF"] ["exponent" "1000.0" "1e3"] ["exponent neg" "0.015" "1.5e-2"]) # Strictness: numeric ops reject non-numbers like Clojure; the integer # predicates reject non-integers; quot/rem/mod reject zero/non-finite. (defspec "numbers / strictness (throws like Clojure)" ["odd? nil" :throws "(odd? nil)"] ["odd? fractional" :throws "(odd? 1.5)"] ["even? inf" :throws "(even? ##Inf)"] ["zero? nil" :throws "(zero? nil)"] ["pos? false" :throws "(pos? false)"] ["neg? keyword" :throws "(neg? :a)"] ["< nil" :throws "(< nil 1)"] ["> with nil" :throws "(> 1 nil)"] ["max non-number" :throws "(max 1 nil)"] ["quot by zero" :throws "(quot 10 0)"] ["quot inf" :throws "(quot ##Inf 1)"] ["< arity-1 any" "true" "(< :anything)"] ["odd? ok" "true" "(odd? 3)"] ["< ok" "true" "(< 1 2 3)"] ["quot ok" "3" "(quot 10 3)"]) (defspec "numbers / printing of inf & nan" ["str Infinity" "\"Infinity\"" "(str ##Inf)"] ["str -Infinity" "\"-Infinity\"" "(str ##-Inf)"] ["str NaN" "\"NaN\"" "(str ##NaN)"] ["pr-str Infinity" "\"Infinity\"" "(pr-str ##Inf)"] ["inf inside coll" "\"[Infinity]\"" "(str [##Inf])"]) (defspec "numbers / bit-ops & math" ["bit-and" "4" "(bit-and 12 6)"] ["bit-or" "14" "(bit-or 12 6)"] ["bit-xor" "10" "(bit-xor 12 6)"] ["bit-shift-left" "8" "(bit-shift-left 1 3)"] ["bit-shift-right" "2" "(bit-shift-right 8 2)"] ["bit-set" "8" "(bit-set 0 3)"] ["bit-clear" "13" "(bit-clear 15 1)"] ["bit-test true" "true" "(bit-test 4 2)"] ["bigint 64-bit" "\"9000000000\"" "(str (bigint 9000000000))"]) (defspec "numbers / random (invariants — non-deterministic)" ["rand-int in range" "true" "(let [r (rand-int 5)] (and (integer? r) (>= r 0) (< r 5)))"] ["rand-int zero" "0" "(rand-int 1)"] ["rand in [0,1)" "true" "(let [r (rand)] (and (>= r 0) (< r 1)))"] ["rand n in [0,n)" "true" "(let [r (rand 10)] (and (>= r 0) (< r 10)))"] ["rand-nth member" "true" "(contains? #{:a :b :c} (rand-nth [:a :b :c]))"] ["rand-nth single" ":x" "(rand-nth [:x])"]) # Clojure 1.11 string->scalar parsers: nil on malformed, throw on non-string. (defspec "numbers / parse fns (1.11)" ["parse-long" "42" "(parse-long \"42\")"] ["parse-long negative" "-7" "(parse-long \"-7\")"] ["parse-long plus" "7" "(parse-long \"+7\")"] ["parse-long float nil" "nil" "(parse-long \"1.5\")"] ["parse-long hex nil" "nil" "(parse-long \"0x10\")"] ["parse-long empty nil" "nil" "(parse-long \"\")"] ["parse-long junk nil" "nil" "(parse-long \"12ab\")"] ["parse-long throws" :throws "(parse-long 42)"] ["parse-double" "1.5" "(parse-double \"1.5\")"] ["parse-double int" "4.0" "(parse-double \"4\")"] ["parse-double sci" "1500.0" "(parse-double \"1.5e3\")"] ["parse-double neg" "-0.5" "(parse-double \"-0.5\")"] ["parse-double junk" "nil" "(parse-double \"abc\")"] ["parse-double trail" "nil" "(parse-double \"1.5x\")"] ["parse-double throws" :throws "(parse-double :k)"] ["parse-boolean true" "true" "(parse-boolean \"true\")"] ["parse-boolean false" "false" "(parse-boolean \"false\")"] ["parse-boolean case" "nil" "(parse-boolean \"True\")"] ["parse-boolean junk" "nil" "(parse-boolean \"yes\")"] ["parse-boolean throws" :throws "(parse-boolean true)"]) # Jolt numbers don't overflow, so the auto-promoting (') and unchecked # variants are aliases of the checked ops (overlay defs, core/20-coll.clj). (defspec "numbers / promoting & unchecked aliases" ["+'" "3" "(+' 1 2)"] ["-'" "3" "(-' 5 2)"] ["*'" "12" "(*' 3 4)"] ["inc'" "6" "(inc' 5)"] ["dec'" "4" "(dec' 5)"] ["unchecked-add" "5" "(unchecked-add 2 3)"] ["unchecked-add-int" "5" "(unchecked-add-int 2 3)"] ["unchecked-subtract" "3" "(unchecked-subtract 5 2)"] ["unchecked-subtract-int" "3" "(unchecked-subtract-int 5 2)"] ["unchecked-multiply" "12" "(unchecked-multiply 3 4)"] ["unchecked-multiply-int" "12" "(unchecked-multiply-int 3 4)"] ["unchecked-negate" "-5" "(unchecked-negate 5)"] ["unchecked-negate-int" "-5" "(unchecked-negate-int 5)"] ["unchecked-inc" "2" "(unchecked-inc 1)"] ["unchecked-inc-int" "2" "(unchecked-inc-int 1)"] ["unchecked-dec" "0" "(unchecked-dec 1)"] ["unchecked-dec-int" "0" "(unchecked-dec-int 1)"] ["unchecked-divide-int" "3" "(unchecked-divide-int 7 2)"] ["unchecked-divide-int negative truncates toward zero" "-3" "(unchecked-divide-int -7 2)"] ["unchecked-divide-int by zero throws" :throws "(unchecked-divide-int 1 0)"] ["unchecked-remainder-int" "1" "(unchecked-remainder-int 7 2)"] ["unchecked-remainder-int negative" "-1" "(unchecked-remainder-int -7 2)"] ["unchecked-int truncates" "3" "(unchecked-int 3.7)"] ["unchecked-int negative" "-3" "(unchecked-int -3.7)"] ["unchecked-long" "3" "(unchecked-long 3.7)"] ["int? on integer" "true" "(int? 5)"] ["int? on double" "false" "(int? 5.5)"] ["int? on non-number" "false" "(int? \"5\")"] ["num passes a number through" "5" "(num 5)"] ["num on a double" "5.5" "(num 5.5)"] ["num throws on non-number" :throws "(num \"x\")"]) # == is numeric equality: single arg is trivially true (Clojure's 1-arity # never inspects the value); 2+ args must be numbers. (defspec "numbers / == numeric equality" ["== single arg" "true" "(== :a)"] ["== equal" "true" "(== 2 2)"] ["== unequal" "false" "(== 2 3)"] ["== chained" "true" "(== 2 2 2)"] ["== chained unequal" "false" "(== 2 2 3)"] ["== int and double" "true" "(== 1 1.0)"] ["== throws on non-number" :throws "(== 1 :a)"] ["== throws on two keywords" :throws "(== :a :a)"]) # Variadic bit ops (canonical Clojure arities: binary host op folded over the # rest). Shift/clear/set/flip/test stay 2-arg, as in Clojure. (defspec "numbers / variadic bit ops" ["bit-and 2" "4" "(bit-and 12 6)"] ["bit-and 3" "4" "(bit-and 12 6 7)"] ["bit-and 4" "0" "(bit-and 12 6 7 3)"] ["bit-or 3" "7" "(bit-or 1 2 4)"] ["bit-xor 3" "7" "(bit-xor 1 2 4)"] ["bit-xor folds left" "1" "(bit-xor 5 6 2)"] ["bit-and-not 2" "8" "(bit-and-not 12 6)"] ["bit-and-not 3" "8" "(bit-and-not 12 6 3)"] ["bit-and single arg throws" :throws "(bit-and 5)"] ["bit-or keeps binary fast path" "3" "(bit-or 1 2)"])