123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 | test "numeric literals are exact"
>> 0.1 + 0.2
assert 0.1 + 0.2 == 0.3
assert 0.1 + 0.2 - 0.3 == 0
assert 1.1 * 3 == 3.3
assert 3.15 == 63/20.
test "integer division with `/` is exact"
>> 1/3
assert 1/3 == 1./3.
assert 7/2 == 3.5
assert (1/3) * 3 == 1
assert Int64(1) / Int64(3) == 1/3
# The integer quotient uses the floored division operator `//`:
assert 7//2 == 3
assert 7//2 + 7 mod 2 * 0 == 3
test "constant arithmetic folds exactly"
# These all compile to a single Num constant (usually a NUMBER_SMALL
# immediate) rather than runtime arithmetic; see fold_num_constant.
# The values must be identical to what the runtime would compute.
assert 1/3 + 1/6 == 0.5
assert 0.1 + 0.1 == 1/5
assert 22/7 > 3
assert (1/3) * 3 == 1
assert -7/2 == -3.5
assert 2 ^ 100 + 0. == (2.).power(100)
assert 7.5 mod 2 == 1.5
assert 7.5 // 2 == 3
assert 1/3 - 1/3 == 0
test "Euclidean division and modulus on Num"
# Same invariants as the integer types: x == y*(x//y) + (x mod y), with
# the remainder always non-negative.
assert 7.5 // 2 == 3
assert 7.5 mod 2 == 1.5
assert -7.5 // 2 == -4
assert -7.5 mod 2 == 0.5
assert 7.5 // -2 == -3
assert 7.5 mod -2 == 1.5
for pair in [[7.5, 2.], [-7.5, 2.], [7.5, -2.], [-7.5, -2.], [1./3., 0.25]]
x := pair[1]!
y := pair[2]!
assert x == y * (x // y) + (x mod y)
assert (x mod y) >= 0
test "fractions stay exact"
>> 1./3.
assert 1./3. == 1./3.
assert (1./3.) * 3 == 1
assert (1./3.) + (1./6.) == 0.5
assert (1./7.) * 7 == 1
test "big values don't overflow or lose precision"
>> (2.).power(100)
assert (2.).power(100) == 1267650600228229401496703205376
assert (2.).power(100) / (2.).power(100) == 1
assert (1./(2.).power(100)) * (2.).power(100) == 1
test "irrational values are exact"
>> (2.).sqrt()!
assert (2.).sqrt()! * (2.).sqrt()! == 2
assert (8.).sqrt()! == (2.).sqrt()! * 2
assert (16.).sqrt()! == 4
assert (2.).sqrt()!.is_rational() == no
assert (4.).sqrt()!.is_rational() == yes
test "pi"
>> Num.PI
assert Num.PI.sin() == 0
assert Num.PI.cos() == -1
assert Num.TAU == 2 * Num.PI
assert (Num.PI / 2).sin() == 1
assert Num.PI.is_rational() == no
test "degrees and percents"
assert 180deg == Num.PI
assert 90deg == Num.PI / 2
assert 50% == 0.5
assert 25% == 1./4.
test "display shows exact values"
# A terminating decimal prints as one; anything else prints in the exact
# form it has, never as a rounded decimal.
assert "$(0.1 + 0.2)" == "0.3"
assert "$(1./3.)" == "1/3"
assert "$((2.).sqrt()!)" == "sqrt(2)"
assert "$(Num.PI)" == "pi"
assert "$(2. + 2.)" == "4"
test "approximating to a requested number of digits"
>> (1/3).digits(10)
# An inexact result gets an ellipsis, and its digits are a truncated
# prefix of the true expansion (never rounded: an ellipsis promises the
# real expansion continues from what's shown).
assert (1/3).digits(10) == "0.3333333333…"
assert (2/3).digits(10) == "0.6666666666…"
assert (1/3).digits(0) == "0…"
assert Num.PI.digits(10) == "3.1415926535…"
assert (2.).sqrt()!.digits(10) == "1.4142135623…"
assert (-2/3).digits(10) == "-0.6666666666…"
# The digits never round, whatever the ellipsis. Rounding is what
# .round() is for:
assert (2/3).digits(10, ellipsis="") == "0.6666666666"
assert (2/3).digits(10, ellipsis="...") == "0.6666666666..."
# An exact value has no ellipsis, and stops early rather than padding:
assert (0.25).digits(10) == "0.25"
test "is_exact reports whether digits capture the value"
assert (0.25).is_exact(10)
assert not (1./3.).is_exact(10)
assert not Num.PI.is_exact(1000)
assert (0.1 + 0.2).is_exact(1)
test "symbolic and TeX forms"
assert (1./3.).symbolic() == "1/3"
assert (2.).sqrt()!.symbolic() == "sqrt(2)"
assert Num.PI.symbolic() == "pi"
assert (1./3.).tex() == "\\frac{1}{3}"
assert (2.).sqrt()!.tex() == "\\sqrt{2}"
test "converting to a float approximates by default"
>> Float64(1./3.)
assert Float64(0.5) == 0.5
assert Float64(1./3.) == Float64(1) / Float64(3)
assert Float32(0.5) == Float32(0.5)
# A float that happens to be exact converts under truncate=no too:
assert Float64(0.5, truncate=no) == 0.5
test "converting to an integer is strict by default"
>> Int(4.)
assert Int(4.) == 4
assert Int(4.9, truncate=yes) == 4
assert Int(-4.9, truncate=yes) == -4
assert Int((2.).power(100)) == 1267650600228229401496703205376
assert Int64(4.) == 4
assert Byte(255.) == Byte(0xff)
assert Int((4.5).floor()) == 4
assert Int((4.5).ceil()) == 5
test "constructing a Num is always exact"
assert Num(5) == 5
assert Num(Int64(5)) == 5
assert Num(Float64(0.5)) == 0.5
assert Num(yes) == 1
# Float64(0.1) is the double, not a tenth, so this is not 0.1:
assert Num(Float64(0.1)) != 0.1
test "parsing"
assert Num.parse("1.5")! == 1.5
assert Num.parse("0.1")! == 0.1
assert Num.parse("22/7")! == 22./7.
assert Num.parse("nope") == none
test "rounding"
assert (4.5).floor() == 4
assert (4.5).ceil() == 5
assert (-4.5).trunc() == -4
assert (2.5).round() == 2
assert (3.5).round() == 4
assert Num.PI.floor() == 3
test "a number ending in a dot takes methods"
# `12.` is a Num, so `12..round()` reads as `(12.).round()`. One dot then
# an identifier stays an Int method: `12.sqrt()` is sqrt of the integer.
assert 12..round() == 12
assert 5..sqrt()! == (5.).sqrt()!
assert 12.5.round() == 12
assert 12.sqrt()! == 3
test "rounding to an increment"
# The nearest multiple of anything: a penny, a nickel, a third, a thousand.
assert (3.14159).round(0.01) == 3.14
assert (0.13).round(0.05) == 0.15
assert (0.5).round(1/3) == 2/3
assert (1234567.).round(1000) == 1235000
assert Num.PI.round(0.001) == 3.142
assert (-3.14159).round(0.01) == -3.14
# Ties go to the even multiple, same as the integer default:
assert (0.125).round(0.05) == 0.1
test "rounding to a zero increment panics"
_ := (1.5).round(0.)
fails "Can't round to the nearest zero"
test "comparisons"
assert 0.1 + 0.2 <= 0.3
assert 0.1 + 0.2 >= 0.3
assert 1./3. < 0.34
assert 1./3. > 0.33
assert (2.).sqrt()! < 1.5
assert (2.).sqrt()! > 1.4
assert ((1./3.) <> (1./3.)) == Int32(0)
test "distinct irrationals still order correctly"
# The structural shortcut below must not swallow values that only look
# alike: these all have different symbolic forms, so they take the
# refinement path and have to come out in the right order.
assert (2.).sin() != (3.).sin()
assert (2.).sin() != (2.).cos()
assert (2.).sin() > (3.).sin()
assert Num.PI < Num.TAU
assert (2.).sqrt()! < (3.).sqrt()!
test "equality runs out exactly where the digits do"
# Two values whose difference is a bare rational cancel exactly, however
# small that difference is, with no refinement and no threshold:
assert Num.PI != Num.PI + (10.).power(-50)
# When the difference *can't* be cancelled, it has to be refined instead,
# and refinement stops at the same 40 digits that decide equality. Well
# above that, the two are distinguished:
a := (2.).sin() + (3.).sin()
b := (3.).sin() + (2.).sin()
assert a + (10.).power(-20) != b
# Below it, they agree to every digit that counts, so they're equal --
# this is the documented rule, not an artifact of how hard the refiner
# happened to work before giving up:
assert a + (10.).power(-50) == b
test "identical expressions are equal"
# Two separately built irrationals with the same structure are the same
# value, and need no numeric refinement to say so: number_compare's
# tier 3 compares symbolic forms before it tries to refine a difference
# it can never prove nonzero.
assert (2.).sin() == (2.).sin()
assert (2.).sin() + Num.PI == (2.).sin() + Num.PI
assert (2.).sin() * Num.PI == (2.).sin() * Num.PI
assert (2.).sin() != (3.).sin()
test "equal values are equal however they're written"
assert (2.).sqrt()! == (8.).sqrt()! / 2
assert (2.).sqrt()! * (2.).sqrt()! == 2
assert Num.PI * 2 == Num.TAU
# Proving this symbolically takes reasoning no engine does in full, so it
# falls back to agreeing to 40 digits, which it does.
assert (3. + 2 * (2.).sqrt()!).sqrt()! == 1 + (2.).sqrt()!
test "irrationals work as table keys"
>> t := {(2.).sin(): "sin 2", Num.PI: "pi", (2.).sqrt()!: "root 2"}
assert t[(2.).sin()]! == "sin 2"
assert t[Num.PI]! == "pi"
assert t[(8.).sqrt()! / 2]! == "root 2"
test "a heavily shared expression stays small"
# Doubling an irrational builds a DAG whose unfolded form is 2^n; the
# symbolic form names repeated subexpressions instead of copying them.
x := (1.).sin()
for i in 1.to(30)
x = x + x
assert x.symbolic().length < 1000
bytes := serialize(x)
roundtrip := deserialize:Num(bytes)!
assert roundtrip == x
test "methods with a restricted domain return none"
assert (-1.).sqrt() == none
assert (0.).log() == none
assert (-1.).log() == none
assert (2.).asin() == none
assert (2.).acos() == none
assert (0.).inverse() == none
assert (1.).log() == 0
assert (4.).sqrt() == 2
test "optional Num"
>> maybe : Num? = none
assert maybe == none
assert maybe != 0.0
assert (2.).sqrt()! * (2.).sqrt()! == 2
>> present : Num? = 0.5
assert present! == 0.5
assert (maybe or 1.5) == 1.5
assert (present or 1.5) == 0.5
test "exact values as table keys"
# Mathematically equal values are the same key, whatever shape they have.
>> t := {0.5: "half", 1./3.: "third"}
assert t[1./2.]! == "half"
assert t[(2.).sqrt()! / (2.).sqrt()! / 2]! == "half"
assert t[1./3.]! == "third"
test "lists of exact values"
>> xs := [1./3., 0.5, 0.25]
assert xs.sorted() == [0.25, 1./3., 0.5]
assert xs.reversed() == [0.25, 0.5, 1./3.]
assert xs.has(0.5)
test "converting a fraction to an Int without truncating panics"
_ := Int(1./3.)
fails "Could not convert this Num to an Int without truncation"
test "converting a fraction to a Float64 without truncating panics"
_ := Float64(1./3., truncate=no)
fails "Could not convert this Num to a Float64 without losing precision"
test "serialization round-trips exact rationals"
>> obj := [0.5, 1./3., 0.1 + 0.2, (2.).power(100), -7./2., 0.]
>> bytes := serialize(obj)
>> roundtrip := deserialize:[Num](bytes)!
assert roundtrip == obj
test "serialization round-trips irrationals"
irrationals := [(2.).sqrt()!, Num.PI, Num.PI * 2, (2.).sin(), (2.).exp(), (3.).log()!, Num.PI + (2.).sqrt()!, Num.PI * Num.PI * Num.PI, (1. + Num.PI) * (2.).sin(), (1.).atan()]
for x in irrationals
bytes := serialize(x)
roundtrip := deserialize:Num(bytes)!
assert roundtrip.symbolic() == x.symbolic()
assert roundtrip == x
test "dividing by zero panics"
_ := 1.0 / 0.0
fails "division by zero"
test "the modulo of zero panics"
_ := 1.0 mod 0.0
fails "division by zero"
test "force-unwrapping the square root of a negative panics"
_ := (-1.).sqrt()!
fails "This was expected to be a value, but it's `none`"
# As with the integer notations in test/integers.tm, we need to test that
# the different numeric forms are equivalent and roundtrip with `tomo format --check`
test "num literal notations"
assert .5 == 0.5
assert 1. == 1
assert 6.02e23 == 602000000000000000000000
assert 1.5e-3 == 0.0015
assert 1_000.5 == 1000.5
assert 0.5% == 0.005
assert 12..round() == 12
|