diff --git a/src/Infinities.jl b/src/Infinities.jl index 95273de..c5e4e11 100644 --- a/src/Infinities.jl +++ b/src/Infinities.jl @@ -1,8 +1,10 @@ module Infinities -import Base: angle, isone, iszero, isinf, isfinite, abs, one, oneunit, zero, isless, inv, - +, -, *, ^, ==, <, ≤, >, ≥, fld, cld, div, mod, min, max, sign, signbit, - string, show, promote_rule, convert, getindex, tryparse, +import Base: angle, isone, iszero, isinf, isfinite, isnan, isreal, abs, one, oneunit, zero, isless, isequal, inv, + +, -, *, /, ^, ==, <, ≤, >, ≥, fld, cld, div, mod, rem, divrem, min, max, + sign, signbit, isapprox, + string, show, promote_rule, convert, getindex, tryparse, conj, + isinteger, round, floor, ceil, trunc, float, Bool, Integer export ∞, ℵ₀, ℵ₁, RealInfinity, ComplexInfinity, InfiniteCardinal, NotANumber, PositiveInfinity, NegativeInfinity @@ -10,11 +12,17 @@ export ∞, ℵ₀, ℵ₁, RealInfinity, ComplexInfinity, InfiniteCardinal, N # export Infinity """ -NotANumber() + NotANumber() -represents something that is undefined, for example, `0 * ∞`. +Construct the undefined value, for example the result of `0 * ∞`. + +Every float type has a `NaN` of its own. This one belongs to none of them. """ -struct NotANumber <: Number end +struct NotANumber <: Real end + +(::Type{T})(::NotANumber) where {T<:AbstractFloat} = T(NaN) +float(::NotANumber) = NaN +Base.hash(::NotANumber, h::UInt)::UInt = hash(NaN, h) """ @@ -88,47 +96,90 @@ zero(::Type{RealInfinity}) = 0.0 # ComplexInfinity ####### -# angle is π*a where a is (false==0) and (true==1) - """ -ComplexInfinity(signbit) + ComplexInfinity(turns::UInt64) + ComplexInfinity(; halfturns::Real = 0) + +Construct an infinity in the complex plane, pointing in a direction held as a count of +`2^-64` turns. + +The count wraps at a full turn, so the stored `UInt64` and the directions are bijective. +`0x0` points along the positive real axis. Values increase counterclockwise. +`0x8000000000000000` points along the negative real axis. Use `reinterpret(UInt64, x)` to +read out the exact value. + +Multiplying by `∞` takes the direction from the other operand, which usually reads better +than naming an angle: -represents an infinity in the complex plane with the angle -specified by `π * signbit`. The use of the name `signbit` is -for consistency with `RealInfinity`. + im*∞ # cispi(0.5)∞ + (1+im)*∞ # cispi(0.25)∞ + exp(im*π/4)*∞ # the same direction again + +Those forms and the `halfturns` keyword go through `angle`, so they round. It is exact on +the axes and at a quarter turn, but `exp(im*π/8)*∞` lands 256 counts past an eighth turn. +Provide the `UInt64` when you have an off-axis value where accuracy matters. """ -struct ComplexInfinity{T<:Real} <: Number - signbit::T +struct ComplexInfinity <: Number + turns::UInt64 + ComplexInfinity(turns::UInt64) = new(turns) end -ComplexInfinity{T}() where T = ComplexInfinity(zero(T)) -ComplexInfinity() = ComplexInfinity{Bool}() -ComplexInfinity{T}(::Infinity) where T<:Real = ComplexInfinity{T}() +# A full turn fills the `UInt64` range, so a half turn is 2^63 units. +const _HALFTURN = UInt64(2)^63 # the negative real axis +# `_turns` and `_halfturns` are inverse: half turns in, count out, and back again. +# `mod` returns 2 itself for a tiny negative angle, since 2 + x rounds back to 2. A full +# turn is the direction zero. Testing `== 2` rather than `< 2` still lets `NaN` throw. +@inline _turns(halfturns::Real) = round(UInt64, (h = mod(halfturns, 2); h == 2 ? zero(h) : h) * 0x1p63) +# Scaling by 2^63 needs 63 bits beyond the numerator, so `Int128` has room for any `Int64`. +_turns(halfturns::Rational) = round(BigInt, mod(halfturns, 2) * big(2)^63) % UInt64 +_turns(halfturns::Rational{<:Base.BitInteger64}) = + round(Int128, mod(halfturns, 2) * Int128(2)^63) % UInt64 +# `Base` puts an angle in `(-π, π]`, so past the half turn the count reads as negative. +@inline _halfturns(x::ComplexInfinity) = + x.turns == _HALFTURN ? 1.0 : reinterpret(Int64, x.turns) / 0x1p63 + +ComplexInfinity(; halfturns::Real = 0) = ComplexInfinity(_turns(halfturns)) ComplexInfinity(::Infinity) = ComplexInfinity() -ComplexInfinity{T}(x::RealInfinity) where T<:Real = ComplexInfinity{T}(signbit(x)) -ComplexInfinity(x::RealInfinity) = ComplexInfinity(signbit(x)) -ComplexInfinity{T}(x::ComplexInfinity) where T<:Real = ComplexInfinity(T(signbit(x))) # ambiguity fix +ComplexInfinity(x::RealInfinity) = ComplexInfinity(_directionof(x)) +ComplexInfinity(x::ComplexInfinity) = x + +signbit(y::ComplexInfinity) = y.turns == _HALFTURN +isreal(y::ComplexInfinity) = iszero(y.turns) || signbit(y) -signbit(y::ComplexInfinity{Bool}) = y.signbit -signbit(y::ComplexInfinity{<:Integer}) = !(mod(y.signbit,2) == 0) -signbit(y::ComplexInfinity) = y.signbit +# `Base` converts a `Complex` to a `Real` the same way, and throws the same error off the axis. +RealInfinity(x::ComplexInfinity) = isreal(x) ? RealInfinity(signbit(x)) : + throw(InexactError(:RealInfinity, RealInfinity, x)) -convert(::Type{ComplexInfinity{T}}, ::Infinity) where T = ComplexInfinity{T}() convert(::Type{ComplexInfinity}, ::Infinity) = ComplexInfinity() -convert(::Type{ComplexInfinity{T}}, x::RealInfinity) where T = ComplexInfinity{T}(x) convert(::Type{ComplexInfinity}, x::RealInfinity) = ComplexInfinity(x) -sign(y::ComplexInfinity{<:Integer}) = mod(y.signbit,2) == 0 ? 1 : -1 -angle(x::ComplexInfinity) = π*x.signbit +sign(y::ComplexInfinity) = cispi(_halfturns(y)) +angle(x::ComplexInfinity) = _halfturns(x) * π +abs(::ComplexInfinity) = ∞ +conj(y::ComplexInfinity) = ComplexInfinity(-y.turns) -show(io::IO, x::ComplexInfinity) = print(io, "exp($(x.signbit)*im*π)∞") +# An exact zero has to stay finite, `Inf * 0` being a `NaN`. +@inline _ray(c) = iszero(c) ? c : copysign(Inf, c) +# `Complex` reaches only the eight rays of its two saturating parts, so the direction lands on the nearest of them. +function float(x::ComplexInfinity) + s, c = sincospi(_halfturns(x)) + complex(_ray(c), _ray(s)) +end + +# The readable form names an angle, which recovers most counts but not all, so it is used +# only where reading it back gives the same direction. +function show(io::IO, x::ComplexInfinity) + h = _halfturns(x) + _directionof(cispi(h)) == x.turns ? print(io, "cispi($h)∞") : + print(io, "ComplexInfinity(", repr(x.turns), ")") +end -one(::Type{<:ComplexInfinity}) = one(ComplexF64) -oneunit(::Type{<:ComplexInfinity}) = oneunit(ComplexF64) +one(::Type{ComplexInfinity}) = one(ComplexF64) +oneunit(::Type{ComplexInfinity}) = oneunit(ComplexF64) oneunit(::ComplexInfinity) = oneunit(ComplexF64) zero(::ComplexInfinity) = zero(ComplexF64) -zero(::Type{<:ComplexInfinity}) = zero(ComplexF64) +zero(::Type{ComplexInfinity}) = zero(ComplexF64) # `isequal` implies equal hashes, so the infinities have to hash like the float @@ -138,12 +189,11 @@ Base.hash(::Infinity, h::UInt)::UInt = hash(Inf, h) Base.hash(::PositiveInfinity, h::UInt)::UInt = hash(Inf, h) Base.hash(::NegativeInfinity, h::UInt)::UInt = hash(-Inf, h) -# Equality of ComplexInfinity is equality of the angle, hence so is the hash. +# The two real directions have to hash like the real infinities they compare equal to. function Base.hash(x::ComplexInfinity, h::UInt)::UInt - θ = angle(x) - θ == angle(PositiveInfinity()) && return hash(Inf, h) - θ == angle(NegativeInfinity()) && return hash(-Inf, h) - hash(ComplexInfinity, hash(θ, h)) + iszero(x.turns) && return hash(Inf, h) + x.turns == _HALFTURN && return hash(-Inf, h) + hash(ComplexInfinity, hash(x.turns, h)) end diff --git a/src/algebra.jl b/src/algebra.jl index 669cc4a..534d908 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -6,28 +6,36 @@ @inline infpromote(x::RealInfinity, y::Union{Integer, Rational}) = (x, float(y)) @inline infpromote(x::Union{Integer, Rational}, y::RealInfinity) = (float(x), y) @inline infpromote(x::RealInfinity, ::InfiniteCardinal) = (x, ∞) +# `Base` promotes every `Real` to `BigFloat`, which would convert the infinity away. +@inline infpromote(x::BigFloat, y::Union{Infinity,RealInfinity}) = (x, y) +@inline infpromote(x::Union{Infinity,RealInfinity}, y::BigFloat) = (x, y) # sign +(::Infinity) = RealInfinity() -(::Infinity) = RealInfinity(true) -(y::RealInfinity) = RealInfinity(!signbit(y)) --(y::ComplexInfinity{B}) where B<:Integer = sign(y) == 1 ? ComplexInfinity(one(B)) : ComplexInfinity(zero(B)) +-(y::ComplexInfinity) = ComplexInfinity(y.turns ⊻ _HALFTURN) +(x::InfiniteCardinal) = x -(::InfiniteCardinal) = -∞ # addition @inline toinf(x) = RealInfinity(signbit(x)) -@inline toinf(x::Complex) = ComplexInfinity(angle(x)) +@inline toinf(x::Complex) = ComplexInfinity(_directionof(x)) @inline toinf(x::ComplexInfinity) = x -@inline _infadd(x, y) = angle(x) == angle(y) ? y : throw(ArgumentError("Angles must be the same to add ∞")) +# The undefined value that matches the operands, as `Base` returns `NaN` or `NaN + NaN*im`. +@inline _undefined(x, y) = + x isa ExtendedComplex || y isa ExtendedComplex ? ComplexNotANumber : NotANumber() + +@inline _infadd(x, y) = angle(x) == angle(y) ? y : _undefined(x, y) @inline __add(x, y::AllInfinities) = isinf(x) ? _infadd(toinf(x), y) : y @inline __add(x::Integer, y::InfiniteCardinal) = max(x, y) -@inline _add(x, y) = __add(infpromote(x, y)...) +# A `NaN` argument makes the result undefined. Types with no `NaN` fold the test away. +@inline _add(x, y) = isnan(x) ? _undefined(x, y) : __add(infpromote(x, y)...) +(x::Number, y::AllInfinities) = _add(x, y) +(x::AllInfinities, y::Number) = _add(y, x) @@ -44,16 +52,21 @@ # multiplication -@inline _sb(x) = signbit(x) -@inline _sb(x::Complex) = angle(x)/π # overloading `signbit` causes type piracy +# The count of the direction a value points in. `_turns` instead reads its argument as a +# number of half turns. +@inline _directionof(x::Real) = signbit(x) ? _HALFTURN : zero(UInt64) +@inline _directionof(x::Complex) = _turns(angle(x) / π) # overloading `signbit` causes type piracy +@inline _directionof(x::ComplexInfinity) = x.turns -@inline __mul(x, y::AllInfinities) = RealInfinity(_sb(x) ⊻ _sb(y)) -@inline __mul(x, y::ComplexInfinity) = ComplexInfinity(_sb(x) + _sb(y)) -@inline __mul(x, y::ComplexInfinity{Bool}) = ComplexInfinity(_sb(x) ⊻ _sb(y)) -@inline __mul(x::Complex, y::ComplexInfinity{Bool}) = ComplexInfinity(_sb(x) + _sb(y)) +@inline __mul(x, y::AllInfinities) = RealInfinity(signbit(x) ⊻ signbit(y)) +@inline __mul(x, y::ComplexInfinity) = ComplexInfinity(_directionof(x) + _directionof(y)) @inline __mul(x::Integer, y::InfiniteCardinal) = x > 0 ? y : throw(ArgumentError("Cannot multiply $x * $y")) -@inline _mul(x, y) = iszero(x) ? throw(ArgumentError("Cannot multiply $x * $y")) : __mul(infpromote(x, y)...) +@inline function _mul(x, y) + isnan(x) && return _undefined(x, y) + iszero(x) && return _undefined(x, y) + __mul(infpromote(x, y)...) +end *(x::Number, y::AllInfinities) = _mul(x, y) *(x::AllInfinities, y::Number) = _mul(y, x) @@ -66,8 +79,17 @@ *(::Infinity, ::Infinity) = ∞ +# division +# `\` needs nothing of its own, `Base` defining it as `y / x`. +@inline _div(x, y) = x * inv(y) + +/(x::AllInfinities, y::Number) = _div(x, y) +/(x::Number, y::AllInfinities) = _div(x, y) +/(x::AllInfinities, y::AllInfinities) = _undefined(x, y) + # mod @inline function _mod(x::Real, y::IntegerInfinities) + isnan(x) && return NotANumber() signbit(x) == signbit(y) || throw(ArgumentError("mod($x,$y) is unbounded")) x end @@ -75,15 +97,25 @@ mod(x::Real, y::IntegerInfinities) = _mod(x, y) mod(::IntegerInfinities, ::Real) = NotANumber() mod(::IntegerInfinities, ::IntegerInfinities) = NotANumber() +# rem, divrem +# `rem` keeps the sign of the dividend, so unlike `mod` it stays bounded either way. +rem(x::Real, ::IntegerInfinities) = isnan(x) ? NotANumber() : x +rem(::IntegerInfinities, ::Real) = NotANumber() +rem(::IntegerInfinities, ::IntegerInfinities) = NotANumber() +# `Base` computes the remainder of two `Integer`s as `a - div(a,b)*b`, which an `InfiniteCardinal` cannot evaluate. +divrem(x::Real, y::IntegerInfinities) = (div(x, y), rem(x, y)) +divrem(x::IntegerInfinities, y::Real) = (div(x, y), rem(x, y)) +divrem(x::IntegerInfinities, y::IntegerInfinities) = (div(x, y), rem(x, y)) + # fld, cld, div -_divinf(T) = zero(T) -_fldinf(x) = signbit(x) ? -one(x) : zero(x) -_cldinf(x) = signbit(x) ? zero(x) : one(x) -div(::T, ::IntegerInfinities) where T <: Real = _divinf(T) +_divinf(x) = isnan(x) ? NotANumber() : zero(x) +_fldinf(x) = isnan(x) ? NotANumber() : signbit(x) ? -one(x) : zero(x) +_cldinf(x) = isnan(x) ? NotANumber() : signbit(x) ? zero(x) : one(x) +div(x::Real, ::IntegerInfinities) = _divinf(x) fld(x::Real, ::IntegerInfinities) = _fldinf(x) cld(x::Real, ::IntegerInfinities) = _cldinf(x) -_inffcd(x, y) = signbit(y) ? -x : x +_inffcd(x, y) = isnan(y) ? NotANumber() : signbit(y) ? -x : x for OP in (:fld,:cld,:div) @eval begin $OP(x::IntegerInfinities, y::Real) = _inffcd(x, y) @@ -94,8 +126,9 @@ end # power # Although the base implementation can cover these cases, it can change overtime and yield inconsistent results. # ref: https://github.com/JuliaMath/Infinities.jl/actions/runs/19993302836/ -_infpow(::PositiveInfinity, p) = ifelse(iszero(p), one(p), ifelse(p > 0, +∞, +zero(p))) +_infpow(::PositiveInfinity, p) = isnan(p) ? NotANumber() : ifelse(iszero(p), one(p), ifelse(p > 0, +∞, +zero(p))) function _infpow(x::NegativeInfinity, p) + isnan(p) && return NotANumber() !isinteger(p) && throw(Base.Math.throw_exp_domainerror(x)) iszero(p) && return one(p) isodd(p) && return ifelse(p > 0, -∞, -zero(p)) @@ -107,4 +140,36 @@ end # inv inv(::Union{Infinity,InfiniteCardinal}) = 0 inv(x::RealInfinity) = inv(float(x)) -inv(x::ComplexInfinity) = zero(ComplexF64) \ No newline at end of file +inv(x::ComplexInfinity) = zero(ComplexF64) + + +# NotANumber +# Anything computed from an undefined value is undefined again, as it is for `NaN`. +for op in (:+, :-, :*, :/, :^, :div, :fld, :cld, :mod, :rem, :min, :max) + for Typ in NotANumberRivals + @eval $op(x::NotANumber, ::$Typ) = x + @eval $op(::$Typ, y::NotANumber) = y + end + for Typ in NotANumberComplexRivals + @eval $op(::NotANumber, ::$Typ) = ComplexNotANumber + @eval $op(::$Typ, ::NotANumber) = ComplexNotANumber + end + @eval $op(x::NotANumber, ::NotANumber) = x +end +for Typ in NotANumberRivals + @eval divrem(x::NotANumber, ::$Typ) = (x, x) + @eval divrem(::$Typ, y::NotANumber) = (y, y) +end +for Typ in NotANumberComplexRivals + @eval divrem(::NotANumber, ::$Typ) = (ComplexNotANumber, ComplexNotANumber) + @eval divrem(::$Typ, ::NotANumber) = (ComplexNotANumber, ComplexNotANumber) +end +divrem(x::NotANumber, ::NotANumber) = (x, x) +# `Base` has its own `^(::Number, ::Integer)`, which a literal exponent also routes through. +^(x::NotANumber, ::Integer) = x +^(::Integer, y::NotANumber) = y +^(x::NotANumber, ::Rational) = x +^(::Irrational{:ℯ}, y::NotANumber) = y +for f in (:+, :-, :abs, :inv, :sign, :conj) + @eval $f(x::NotANumber) = x +end \ No newline at end of file diff --git a/src/ambiguities.jl b/src/ambiguities.jl index 371dd26..10b8ed0 100644 --- a/src/ambiguities.jl +++ b/src/ambiguities.jl @@ -1,10 +1,9 @@ for Typ in (Base.TwicePrecision, AbstractChar, Complex) - @eval begin - RealInfinity(x::$Typ) = throw(MethodError(RealInfinity, x)) - ComplexInfinity{T}(x::$Typ) where T<:Real = ComplexInfinity(T(x)) - end + @eval RealInfinity(x::$Typ) = throw(MethodError(RealInfinity, x)) end -ComplexInfinity{T}(x::ComplexInfinity{T}) where T<:Real = x +# `Base` builds a `TwicePrecision` of any type by adding its two halves, which for two opposed +# directions is undefined. +ComplexInfinity(x::Base.TwicePrecision) = ComplexInfinity(halfturns = Float64(x)) for Typ in (Rational, BigInt, BigFloat) for (op, fop) in ((:<, :_lt), (:≤, :_le)) @@ -19,12 +18,15 @@ for Typ in (Rational, BigInt, BigFloat, Complex, AbstractIrrational) end for Typ in (Complex, Rational, Complex{Bool}, Integer) - @eval +(x::AllInfinities, y::$Typ) = _add(y, x) - @eval +(x::$Typ, y::AllInfinities) = _add(x, y) - @eval -(x::AllInfinities, y::$Typ) = _sub(x, y) - @eval -(x::$Typ, y::AllInfinities) = _sub(x, y) - @eval *(x::AllInfinities, y::$Typ) = _mul(y, x) - @eval *(x::$Typ, y::AllInfinities) = _mul(x, y) + # `_add` and `_mul` dispatch on the infinity being second; `_sub` and `_div` delegate to them. + for (op, fop) in ((:+, :_add), (:*, :_mul)) + @eval $op(x::AllInfinities, y::$Typ) = $fop(y, x) + @eval $op(x::$Typ, y::AllInfinities) = $fop(x, y) + end + for (op, fop) in ((:-, :_sub), (:/, :_div)) + @eval $op(x::AllInfinities, y::$Typ) = $fop(x, y) + @eval $op(x::$Typ, y::AllInfinities) = $fop(x, y) + end end ^(x::RealInfinity, y::Rational) = _infpow(infpromote(x, y)...) @@ -32,10 +34,19 @@ end for Typ in (Rational, ) @eval mod(::IntegerInfinities, ::$Typ) = NotANumber() @eval mod(x::$Typ, y::IntegerInfinities) = _mod(x, y) + @eval rem(::InfiniteCardinal, ::$Typ) = NotANumber() + @eval rem(x::$Typ, ::IntegerInfinities) = x for op in (:fld, :cld, :div) @eval $op(x::InfiniteCardinal, y::$Typ) = _inffcd(x, y) end - @eval div(::T, ::IntegerInfinities) where T <: $Typ = _divinf(T) + @eval div(x::$Typ, ::IntegerInfinities) = _divinf(x) @eval fld(x::$Typ, ::IntegerInfinities) = _fldinf(x) @eval cld(x::$Typ, ::IntegerInfinities) = _cldinf(x) -end \ No newline at end of file +end + +divrem(x::BigInt, y::IntegerInfinities) = (div(x, y), rem(x, y)) + +# an `InfiniteCardinal` is an `Integer`, for which `Base` has its own `isapprox` +isapprox(x::InfiniteCardinal, y::Integer; kwargs...) = x == y +isapprox(x::Integer, y::InfiniteCardinal; kwargs...) = x == y +isapprox(x::InfiniteCardinal, y::InfiniteCardinal; kwargs...) = x == y \ No newline at end of file diff --git a/src/cardinality.jl b/src/cardinality.jl index bd71de2..f18277b 100644 --- a/src/cardinality.jl +++ b/src/cardinality.jl @@ -41,6 +41,9 @@ function Integer(x::ComplexInfinity) ℵ₀ end +# Every cardinal is a positive real infinity, so it points along the positive real axis. +ComplexInfinity(::InfiniteCardinal) = ComplexInfinity() + Base.to_index(::Union{Infinity,InfiniteCardinal{0}}) = ℵ₀ Base.to_shape(::Union{Infinity,InfiniteCardinal{0}}) = ℵ₀ diff --git a/src/compare.jl b/src/compare.jl index 0faada0..42a5b56 100644 --- a/src/compare.jl +++ b/src/compare.jl @@ -16,6 +16,35 @@ _isinf(x::Number, y::AllInfinities) = isinf(x) && _angle(x) == angle(y) # On the real line the direction is a comparison against zero. # `signbit(y)` is constant, so the branch folds away and the check becomes a single instruction. _isinf(x::Real, y::AllRealInfinities) = isinf(x) && (signbit(y) ? x < zero(x) : x > zero(x)) +# A direction in the plane is decided by the count, which is exact where an angle in a +# `Float64` is not: the count has 64 bits and the angle has 53. +_isinf(x::Number, y::ComplexInfinity) = isinf(x) && _directionof(x) == y.turns +_isinf(x::ComplexInfinity, y::AllRealInfinities) = x.turns == _directionof(y) + +# NotANumber +# Undefined compares false against everything, itself included, as `NaN` does. +for op in (:(==), :<, :≤, :>, :≥) + for Typ in (NotANumberRivals..., NotANumberComplexRivals...) + @eval $op(::NotANumber, ::$Typ) = false + @eval $op(::$Typ, ::NotANumber) = false + end + @eval $op(::NotANumber, ::NotANumber) = false +end + +# `isequal` and `hash` still have to identify it, so that a container can hold one. +isequal(::NotANumber, ::NotANumber) = true +for Typ in (NotANumberRivals..., NotANumberComplexRivals...) + @eval isequal(::NotANumber, y::$Typ) = isnan(y) + @eval isequal(x::$Typ, ::NotANumber) = isnan(x) +end + +# The sort order puts it after every value, `NaN` included, so that sorting stays total. +# `isless` is the only operator for which `AllRealInfinities` is not covered by a wider slot. +isless(::NotANumber, ::NotANumber) = false +for Typ in (NotANumberRivals..., NotANumberComplexRivals..., AllRealInfinities) + @eval isless(::NotANumber, ::$Typ) = false + @eval isless(x::$Typ, ::NotANumber) = !isnan(x) +end # == @inline _eq(x, y::InfiniteCardinal) = x == ∞ && y == ℵ₀ @@ -27,6 +56,17 @@ _isinf(x::Real, y::AllRealInfinities) = isinf(x) && (signbit(y) ? x < zero(x) : ==(y::Number, x::AllInfinities) = _eq(y, x) ==(x::AllInfinities, y::AllInfinities) = _infeq(x, y) +# isapprox +# `Base` compares only after promoting, which fails for an infinity and a number. +isapprox(x::AllInfinities, y::Number; kwargs...) = x == y +isapprox(x::Number, y::AllInfinities; kwargs...) = x == y +isapprox(x::AllInfinities, y::AllInfinities; kwargs...) = x == y +for Typ in (Number, AllInfinities) + @eval isapprox(::NotANumber, ::$Typ; kwargs...) = false + @eval isapprox(::$Typ, ::NotANumber; kwargs...) = false +end +isapprox(::NotANumber, ::NotANumber; kwargs...) = false + # isless # `isless` is the sort order. `NaN` sorts after every other value, infinities included. isless(x::AllRealInfinities, y::AllRealInfinities) = signbit(x) && !signbit(y) diff --git a/src/interface.jl b/src/interface.jl index a8a9d97..04094d7 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -1,18 +1,43 @@ const AllInfinities = Union{Infinity, RealInfinity, ComplexInfinity, InfiniteCardinal} -const AllRealInfinities = Union{Infinity, RealInfinity, ComplexInfinity{<:Integer}} -const IntegerInfinities = Union{Infinity, RealInfinity, ComplexInfinity{<:Integer}, InfiniteCardinal} -const ExtendedComplex{T} = Union{Complex{T}, ComplexInfinity{T}} +const AllRealInfinities = Union{Infinity, RealInfinity} +const IntegerInfinities = Union{Infinity, RealInfinity, InfiniteCardinal} +const ExtendedComplex = Union{Complex, ComplexInfinity} iszero(::AllInfinities) = false isinf(::AllInfinities) = true isfinite(::AllInfinities) = false +# `NotANumber` is no value at all, so it is neither finite nor infinite. +isnan(::NotANumber) = true +isinf(::NotANumber) = false +isfinite(::NotANumber) = false +iszero(::NotANumber) = false +isone(::NotANumber) = false +isinteger(::NotANumber) = false +signbit(::NotANumber) = false + +# Undefined wins against every second argument, so it needs a method wherever `Base` or this +# package owns a slot of its own. A narrower slot simply beats a wider one. `Number` looks +# redundant against the types here, but without it a foreign `Number` reaches `Base`'s +# promoting fallback instead. +const NotANumberRivals = (Number, Real, AbstractFloat, AbstractIrrational, AllInfinities, + IntegerInfinities, RealInfinity, + InfiniteCardinal) +# A complex operand makes the undefined result complex, as it does over the floats. +const NotANumberComplexRivals = (Complex, Complex{Bool}, ComplexInfinity) +const ComplexNotANumber = complex(NotANumber(), NotANumber()) + +# `InfiniteCardinal` is absent because `Base` already returns `true` for it through `Integer`. +isinteger(::Union{Infinity, RealInfinity, ComplexInfinity}) = false +for f in (:round, :floor, :ceil, :trunc) + @eval $f(x::Union{AllInfinities, NotANumber}; kwargs...) = x +end +round(x::Union{AllInfinities, NotANumber}, ::RoundingMode; kwargs...) = x + # `Infinity` is positive, so it has no common type with `NegativeInfinity` (as is already the case for `PositiveInfinity`). promote_rule(::Type{Infinity}, ::Type{PositiveInfinity}) = PositiveInfinity -promote_rule(::Type{Infinity}, ::Type{ComplexInfinity{T}}) where T = ComplexInfinity{T} -promote_rule(::Type{<:RealInfinity}, ::Type{ComplexInfinity{T}}) where T = ComplexInfinity{T} -promote_rule(::Type{ComplexInfinity{T}}, ::Type{<:RealInfinity}) where T<:Integer = ComplexInfinity{T} -promote_rule(::Type{ComplexInfinity{T}}, ::Type{ComplexInfinity{S}}) where {T, S} = ComplexInfinity{promote_type(T, S)} +promote_rule(::Type{Infinity}, ::Type{ComplexInfinity}) = ComplexInfinity +promote_rule(::Type{<:RealInfinity}, ::Type{ComplexInfinity}) = ComplexInfinity function tryparse(::Type{NegativeInfinity}, s::AbstractString) i = findfirst(!isspace, s) diff --git a/test/runtests.jl b/test/runtests.jl index a1d5069..ee24e73 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -44,7 +44,7 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test ∞ - 1 ≡ ∞ - 1.0 ≡ ∞ @test *(∞) ≡ ∞ @test ∞*∞ ≡ ∞ - @test_throws ArgumentError ∞ - ∞ + @test ∞ - ∞ ≡ NotANumber() @test one(∞) ≡ one(Infinity) ≡ oneunit(∞) ≡ oneunit(Infinity) ≡ 1 @test zero(∞) ≡ 0 @@ -170,23 +170,17 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test (1∞) + (1∞) ≡ 1∞ @test ∞ + (1∞) ≡ (1∞) + ∞ ≡ 1∞ - @test_throws ArgumentError ∞ + (-∞) - @test_throws ArgumentError (1∞) + (-∞) - @test_throws ArgumentError (-∞) + ∞ + @test ∞ + (-∞) ≡ (1∞) + (-∞) ≡ (-∞) + ∞ ≡ NotANumber() @test ∞ - (-∞) ≡ +∞ @test (-∞) - ∞ ≡ -∞ @test (1∞) - (-∞) ≡ 1∞ @test (-∞) - (1∞) ≡ -∞ - @test_throws ArgumentError ∞ - (1∞) - @test_throws ArgumentError (1∞) - ∞ - @test_throws ArgumentError (1∞) - (1∞) - @test_throws ArgumentError (-∞) - (-∞) - @test_throws ArgumentError 0*∞ - @test_throws ArgumentError 0*(-∞) - @test_throws ArgumentError Inf - RealInfinity() - @test_throws ArgumentError RealInfinity() - Inf + # summing opposite directions is undefined, as it is over the floats + @test ∞ - (1∞) ≡ (1∞) - ∞ ≡ (1∞) - (1∞) ≡ (-∞) - (-∞) ≡ NotANumber() + @test Inf - RealInfinity() ≡ RealInfinity() - Inf ≡ NotANumber() + @test 0*∞ ≡ 0*(-∞) ≡ NotANumber() @test (-∞)*2 ≡ 2*(-∞) ≡ -2 * ∞ ≡ ∞ * (-2) ≡ (-2) * RealInfinity() ≡ -∞ @test (-∞)*2.3 ≡ 2.3*(-∞) ≡ -2.3 * ∞ ≡ ∞ * (-2.3) ≡ (-2.3) * RealInfinity() ≡ -∞ @@ -246,17 +240,46 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], end @testset "ComplexInfinity" begin + # every spelling of the positive real axis is the same value, a cardinal included @test ComplexInfinity(∞) ≡ convert(ComplexInfinity, ∞) ≡ ComplexInfinity() ≡ - ComplexInfinity(false) ≡ ComplexInfinity{Bool}(∞) ≡ ComplexInfinity{Bool}(RealInfinity()) ≡ ComplexInfinity{Bool}(ComplexInfinity()) - - @test convert(ComplexInfinity{Bool}, ∞) ≡ convert(ComplexInfinity, ∞) ≡ ComplexInfinity() - @test convert(ComplexInfinity{Bool}, -∞) ≡ convert(ComplexInfinity, -∞) ≡ -ComplexInfinity() + ComplexInfinity(0x0000000000000000) ≡ ComplexInfinity(RealInfinity()) ≡ + ComplexInfinity(ComplexInfinity()) ≡ ComplexInfinity(ℵ₀) + + @test convert(ComplexInfinity, -∞) ≡ -ComplexInfinity() + # one direction is one value, however it is spelled + @test ComplexInfinity(halfturns = -0.5) ≡ ComplexInfinity(halfturns = 1.5) ≡ -im*∞ + @test ComplexInfinity(halfturns = 1) ≡ ComplexInfinity(halfturns = 3) ≡ ComplexInfinity(-∞) + @test isreal(ComplexInfinity()) && isreal(-ComplexInfinity()) && !isreal((1+im)*∞) + # a rational direction converts without a float step + @test reinterpret(UInt64, ComplexInfinity(halfturns = 2//3)) ≡ 0x5555555555555555 + # a numerator too wide for `Int128` takes the `BigInt` route to the same count + @test ComplexInfinity(halfturns = big(1)//3) ≡ ComplexInfinity(halfturns = 1//3) + @test ComplexInfinity(0x4000000000000000) ≡ ComplexInfinity(halfturns = 1//2) ≡ im*∞ + # `mod` rounds a hair below the axis up to a full turn, which has no count of its own + @test ComplexInfinity(halfturns = -1e-300) ≡ ComplexInfinity(halfturns = 2.0) ≡ ComplexInfinity() + @test complex(1.0, -1e-17)*∞ ≡ ComplexInfinity() + # no count names a direction that is not one, so the conversion has to refuse + for h in (NaN, Inf, -Inf) + @test_throws InexactError ComplexInfinity(halfturns = h) + end + # the count runs forwards, `angle` reports it on `Base`'s branch of `(-π, π]` + for h in (0.0, 0.25, 0.5, 1.0, -0.25, -0.5, -0.75) + @test angle(ComplexInfinity(halfturns = h)) ≡ h*π + @test complex(cospi(h), sinpi(h))*∞ == ComplexInfinity(halfturns = h) + end + # off the axes the angle has to be rounded to reach a count + @test reinterpret(UInt64, exp(im*π/8)*∞) - reinterpret(UInt64, ComplexInfinity(halfturns = 1//8)) ≡ + 0x0000000000000100 + @test angle(exp(im*0.3)*∞) ≈ angle(∞*exp(im*0.3)) ≈ 0.3 + # the count is finer than an angle in a `Float64`, so equality has to read the count + @test ComplexInfinity(0x7fffffffffffffff) ≠ -ComplexInfinity() + @test im*∞ * ComplexInfinity(0x0000000000000001) ≠ im*∞ @test isinf(ComplexInfinity()) @test !isfinite(ComplexInfinity()) @test promote(∞, RealInfinity(), ComplexInfinity()) ≡ ntuple(_ -> ComplexInfinity(), 3) - @test promote_type(Infinity, ComplexInfinity{Bool}) == promote_type(RealInfinity, ComplexInfinity{Bool}) == ComplexInfinity{Bool} + @test promote_type(Infinity, ComplexInfinity) == promote_type(RealInfinity, ComplexInfinity) == ComplexInfinity @test ComplexInfinity(∞) == ∞ @@ -274,16 +297,31 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test ComplexInfinity() + ∞ ≡ ComplexInfinity() + RealInfinity() ≡ ∞ + ComplexInfinity() ≡ RealInfinity() + ComplexInfinity() ≡ ComplexInfinity() - @test ComplexInfinity(true) + ComplexInfinity(true) == ComplexInfinity(true) - @test ComplexInfinity(false) + ComplexInfinity(false) == ComplexInfinity(false) - @test ComplexInfinity(true)+1 == ComplexInfinity(true) - @test ComplexInfinity(false)+1 == ComplexInfinity(false) + @test ComplexInfinity(-∞) + ComplexInfinity(-∞) == ComplexInfinity(-∞) + @test ComplexInfinity() + ComplexInfinity() == ComplexInfinity() + @test ComplexInfinity(-∞)+1 == ComplexInfinity(-∞) + @test ComplexInfinity()+1 == ComplexInfinity() + + # An infinite summand reaches `_infadd` through `toinf`, which has to give half turns + @test complex(Inf, 0.0) + ∞ ≡ ComplexInfinity() + @test complex(-Inf, 0.0) + (-∞) ≡ -ComplexInfinity() + @test complex(0.0, Inf) + im*∞ ≡ im*∞ + @test complex(0.0, -Inf) + (-im*∞) ≡ -im*∞ + @test complex(0.0, Inf) + ∞ ≡ complex(NotANumber(), NotANumber()) + # two infinite parts are the only way an infinite `Complex` points off the axes + for (z, inf) in ((complex(Inf, Inf), (1+im)*∞), (complex(-Inf, Inf), (-1+im)*∞), + (complex(-Inf, -Inf), (-1-im)*∞), (complex(Inf, -Inf), (1-im)*∞)) + @test z + inf ≡ inf + end @test ∞ * ComplexInfinity() ≡ RealInfinity() * ComplexInfinity() ≡ ComplexInfinity() * ∞ ≡ ComplexInfinity() * RealInfinity() ≡ ComplexInfinity() - @test 2.0im*∞ ≡ ∞*2.0im ≡ 2.0im * RealInfinity() ≡ RealInfinity() * 2.0im ≡ ComplexInfinity(1/2) + @test 2.0im*∞ ≡ ∞*2.0im ≡ 2.0im * RealInfinity() ≡ RealInfinity() * 2.0im ≡ im*∞ @test 2ComplexInfinity() ≡ ComplexInfinity()*2 ≡ ComplexInfinity() + # a factor gives the direction it actually has, so rescaling moves it once it rounds + @test 4*(0.3+0.1im)*∞ ≡ (0.3+0.1im)*∞ + @test 3*(0.3+0.1im)*∞ ≢ (0.3+0.1im)*∞ @test exp(im*π/4)*∞ == Inf+im*Inf @test exp(im*π/4)+∞ == ∞ @@ -292,40 +330,68 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test Inf == ComplexInfinity() @test ComplexInfinity() == Inf - @test isless(-ComplexInfinity(), ComplexInfinity()) - @test isless(5, ComplexInfinity()) - @test !isless(ComplexInfinity(), 5) - - @test 5 < ComplexInfinity() && 5 ≤ ComplexInfinity() - @test !(ComplexInfinity() < 5) && !(ComplexInfinity() ≤ 5) - @test 5 > -ComplexInfinity() && 5 ≥ -ComplexInfinity() - @test ComplexInfinity() > 5 && ComplexInfinity() ≥ 5 + # the complex plane carries no order, so these are undefined as they are for `Complex` + for op in (isless, <, ≤, >, ≥, min, max), y in (5, ComplexInfinity(), (1+im)*∞) + @test_throws MethodError op(ComplexInfinity(), y) + @test_throws MethodError op(y, ComplexInfinity()) + end + # a direction on the axis converts, as `Real(::Complex)` does + @test RealInfinity(ComplexInfinity()) ≡ +∞ + @test RealInfinity(-ComplexInfinity()) ≡ -∞ + @test_throws InexactError RealInfinity((1+im)*∞) + @test 5 < RealInfinity(ComplexInfinity()) @test 1 + ComplexInfinity() ≡ 1.0 + ComplexInfinity() ≡ ComplexInfinity() + 1 ≡ ComplexInfinity() + 1.0 ≡ ComplexInfinity() @test 5 * ComplexInfinity() ≡ ComplexInfinity() @test (-5) * ComplexInfinity() ≡ -ComplexInfinity() - @test ComplexInfinity(0.25) * ComplexInfinity(0.5) ≡ ComplexInfinity(0.75) - @test ComplexInfinity(0.0) + ComplexInfinity() ≡ ComplexInfinity() + ComplexInfinity(0.0) ≡ ComplexInfinity(0.0) - - @test mod(ComplexInfinity(), 5) ≡ NotANumber() + @test (1+im)*∞ * (im*∞) ≡ (-1+im)*∞ + @test (2.0+0.0im)*∞ + ComplexInfinity() ≡ ComplexInfinity() + (2.0+0.0im)*∞ ≡ ComplexInfinity() - @test stringmime("text/plain", ComplexInfinity()) == "exp(false*im*π)∞" + @test stringmime("text/plain", ComplexInfinity()) == "cispi(0.0)∞" + # a count an angle cannot name is shown as itself, so every form reads back + @test sprint(show, ComplexInfinity(0x5555555555555555)) == "ComplexInfinity(0x5555555555555555)" + for u in (0x0000000000000000, 0x4000000000000000, 0x5555555555555555, 0xdeadbeefdeadbeef) + @test Core.eval(@__MODULE__, Meta.parse(sprint(show, ComplexInfinity(u)))) ≡ ComplexInfinity(u) + end - @testset "min/max" begin - @test min(ComplexInfinity(), -ComplexInfinity()) ≡ -ComplexInfinity() - @test max(ComplexInfinity(), -ComplexInfinity()) ≡ ComplexInfinity() - @test min(ComplexInfinity(), 5) ≡ min(5,ComplexInfinity()) ≡ 5 - @test max(ComplexInfinity(), 5) ≡ max(5,ComplexInfinity()) ≡ ComplexInfinity() + @testset "integer operations" begin + # an integer operation needs a real, and `Base` defines none of these for a `Complex` + for op in (div, fld, cld, mod, rem), x in (ComplexInfinity(), (1+im)*∞) + @test_throws MethodError op(x, 5) + @test_throws MethodError op(5, x) + end + @test div(RealInfinity(ComplexInfinity()), 5) ≡ +∞ end - @testset "fld/cld/div" begin - @test div(ComplexInfinity(), 5) ≡ fld(ComplexInfinity(), 5) ≡ ComplexInfinity() - @test div(-ComplexInfinity(),2) ≡ -ComplexInfinity() + @test signbit(ComplexInfinity(halfturns = 3)) + @test !signbit(ComplexInfinity(halfturns = 100)) + # `signbit` returns a `Bool` for every angle, as it does over the reals + @test signbit(ComplexInfinity(-∞)) === signbit(-ComplexInfinity()) === true + @test signbit(im*∞) === signbit(ComplexInfinity()) === false + + @testset "abs/sign/conj/-" begin + @test -(im*∞) ≡ -im*∞ + @test -(-(im*∞)) ≡ im*∞ + @test -ComplexInfinity() ≡ ComplexInfinity(-∞) + @test abs(ComplexInfinity()) ≡ abs(im*∞) ≡ ∞ + @test sign(im*∞) ≡ complex(0.0, 1.0) + @test sign(ComplexInfinity()) ≡ complex(1.0, 0.0) + @test sign(ComplexInfinity(-∞)) ≡ complex(-1.0, 0.0) + # conjugation negates the direction, so on the real axis it changes nothing + @test conj((1+im)*∞) ≡ (1-im)*∞ + @test conj(conj((1+im)*∞)) ≡ (1+im)*∞ + @test conj(ComplexInfinity(-∞)) ≡ ComplexInfinity(-∞) end - @test signbit(ComplexInfinity(3)) - @test !signbit(ComplexInfinity(100)) + @testset "float" begin + @test float(ComplexInfinity()) ≡ float((2.0+0.0im)*∞) ≡ complex(Inf, 0.0) + @test float(im*∞) ≡ complex(0.0, Inf) + @test float(ComplexInfinity(-∞)) ≡ complex(-Inf, 0.0) + @test float(-im*∞) ≡ float(ComplexInfinity(halfturns = 3/2)) ≡ complex(0.0, -Inf) + # `Complex` points along eight rays only, so every other angle collapses onto the nearest + @test float((1+im)*∞) ≡ float(ComplexInfinity(0x1000000000000000)) ≡ complex(Inf, Inf) + end end @testset "Set" begin @@ -337,8 +403,11 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @testset "hash" begin infinities = (∞, +∞, -∞, Inf, -Inf, Inf32, -Inf32, Inf16, -Inf16, big(Inf), -big(Inf), - InfiniteCardinal{0}(), ComplexInfinity(false), - ComplexInfinity(true), ComplexInfinity(0.1)) + InfiniteCardinal{0}(), ComplexInfinity(), + ComplexInfinity(-∞), ComplexInfinity(0x1000000000000000), + # counts that an angle in a `Float64` cannot tell apart + ComplexInfinity(0x7fffffffffffffff), + im*∞ * ComplexInfinity(0x0000000000000001)) # isequal must imply equal hashes for a in infinities, b in infinities @@ -379,9 +448,9 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test Base.literal_pow(^, -∞, Val(2)) ≡ (-∞)^2 ≡ +∞ @test Base.literal_pow(^, -∞, Val(-2)) ≡ (-∞)^(-2) ≡ 0.0 - @test Base.literal_pow(^, ComplexInfinity(0.1), Val(0)) ≡ ComplexInfinity(0.1)^0 ≡ 1.0+0.0im - @test Base.literal_pow(^, ComplexInfinity(0.1), Val(1)) ≡ (ComplexInfinity(0.1))^1 ≡ ComplexInfinity(0.1) - @test Base.literal_pow(^, ComplexInfinity(0.1), Val(-1)) ≡ (ComplexInfinity(0.1))^(-1) ≡ 0.0+0.0im + @test Base.literal_pow(^, ComplexInfinity(0x1000000000000000), Val(0)) ≡ ComplexInfinity(0x1000000000000000)^0 ≡ 1.0+0.0im + @test Base.literal_pow(^, ComplexInfinity(0x1000000000000000), Val(1)) ≡ (ComplexInfinity(0x1000000000000000))^1 ≡ ComplexInfinity(0x1000000000000000) + @test Base.literal_pow(^, ComplexInfinity(0x1000000000000000), Val(-1)) ≡ (ComplexInfinity(0x1000000000000000))^(-1) ≡ 0.0+0.0im end @testset "one/zero/oneunit" begin @@ -394,6 +463,79 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test zero(exp(0.1im)∞) ≡ zero(ComplexInfinity) ≡ 0.0+0.0im end + @testset "isinteger/round" begin + infinities = (∞, +∞, -∞, ℵ₀, ComplexInfinity(), (1+im)*∞) + @test !isinteger(∞) && !isinteger(+∞) && !isinteger(-∞) + @test !isinteger(ComplexInfinity()) && !isinteger((1+im)*∞) + @test isinteger(ℵ₀) # an `InfiniteCardinal` is an `Integer` + @test ∞ ∉ 1:5 # `in` asks a range for `isinteger` before comparing + for f in (round, floor, ceil, trunc), x in infinities + @test f(x) ≡ f(x; digits=2) ≡ x + end + for r in (RoundNearest, RoundUp, RoundDown, RoundToZero), x in infinities + @test round(x, r) ≡ round(x, r; digits=2) ≡ x + end + end + + @testset "division" begin + @test ∞ / 2 ≡ 2 \ ∞ ≡ +∞ + @test (-∞) / 2 ≡ ∞ / -2 ≡ -∞ + # a zero divisor keeps the direction, and its own sign is the one that counts + @test ∞ / 0 ≡ ∞ / 0.0 ≡ (-∞) / (-0.0) ≡ +∞ + @test (-∞) / 0 ≡ (-∞) / 0.0 ≡ ∞ / (-0.0) ≡ -∞ + @test im*∞ / 2 ≡ im*∞ + # dividing by a complex turns the direction by its angle + @test (+∞) / (1+im) ≡ (1-im)*∞ + @test 2 / -∞ ≡ -0.0 + @test 2 / ∞ == ∞ \ 2 == 2 / ℵ₀ == 0 # the type follows `inv`, which returns an `Int` for `∞` + # `∞` is positive, so the quotient keeps the dividend exact; a signed infinity + # needs a float to carry `-0.0` + @test (2//3) / ∞ ≡ (2//3) / ℵ₀ ≡ 0//1 + @test (2//3) / (+∞) ≡ 0.0 + @test ∞ / ∞ isa NotANumber + # a complex operand on either side makes the undefined quotient complex + @test ComplexInfinity() / ∞ ≡ ∞ / ComplexInfinity() ≡ + ComplexInfinity() / ComplexInfinity() ≡ complex(NotANumber(), NotANumber()) + @test isnan(NaN / ∞) && isnan(∞ / NaN) + end + + @testset "rem/divrem" begin + @test 3 % ∞ ≡ rem(3, -∞) ≡ 3 % ℵ₀ ≡ 3 + @test -3 % ∞ ≡ -3 # `rem` keeps the sign of the dividend, where `mod(-3, ∞)` is unbounded + @test rem(∞, 3) isa NotANumber && rem(∞, ∞) isa NotANumber + @test isnan(rem(NaN, ∞)) + @test divrem(3, ∞) ≡ (0, 3) && divrem(-3, ∞) ≡ (0, -3) + # `Base` has an `Integer`-only `divrem` that avoids `rem`, and `ℵ₀` is an `Integer` + @test divrem(3, ℵ₀) ≡ (0, 3) + @test divrem(ℵ₀, 3) ≡ (ℵ₀, NotANumber()) + @test divrem(ℵ₀, ℵ₀) ≡ (NotANumber(), NotANumber()) + # `Rational` and `BigInt` bring their own `Base` methods, which need methods of their own + @test rem(1//2, ∞) ≡ rem(1//2, ℵ₀) ≡ 1//2 + @test rem(ℵ₀, 1//2) isa NotANumber + @test divrem(big(3), ∞) == divrem(big(3), ℵ₀) == (0, 3) + end + + @testset "isapprox" begin + @test ∞ ≈ Inf && -∞ ≈ -Inf32 && ℵ₀ ≈ ∞ && ∞ ≈ ∞ + @test !(∞ ≈ 1e300) && !(∞ ≈ -∞) + @test Inf ≈ ∞ && -Inf32 ≈ -∞ && !(1e300 ≈ ∞) # the infinity may stand on either side + # an `InfiniteCardinal` is an `Integer`, for which `Base` has its own `isapprox` + @test ℵ₀ ≈ ℵ₀ && !(ℵ₀ ≈ ℵ₁) + @test !(ℵ₀ ≈ 3) && !(3 ≈ ℵ₀) + @test isapprox(∞, Inf; atol=1) # the keywords are accepted, but nothing is near an infinity + @test !isapprox(∞, 1; atol=∞) + + @testset "infinite tolerance" begin + values = (0, 1, -2, 1.5, 0.0, NaN, Inf, -Inf) + for x in values, y in values, (inf, flt) in ((∞, Inf), (+∞, Inf), (-∞, -Inf), (ℵ₀, Inf)) + @test isapprox(x, y; atol=inf) == isapprox(x, y; atol=flt) + # Skipped over a `Base` bug: its `Integer` method evaluates `rtol * 0` for two zeros, so `isapprox(0, 0; rtol=Inf)` is `false`. + x isa Integer && y isa Integer && iszero(x) && iszero(y) && continue + @test isapprox(x, y; rtol=inf) == isapprox(x, y; rtol=flt) + end + end + end + @testset "float precisions" begin for T in (Float16, Float32, Float64, BigFloat) for inf in (∞, +∞, ComplexInfinity(), ℵ₀) @@ -404,6 +546,8 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test T(-Inf) == inf == T(-Inf) @test T(Inf) ≠ inf end + @test T(2) + ∞ ≡ ∞ + T(2) ≡ ∞ + @test T(2) * +∞ ≡ (+∞)^T(2) ≡ +∞ end end @@ -411,7 +555,7 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], # ℵ₁ points in the same direction as ∞, even though `ℵ₁ == ∞` is false positive = (∞, +∞, ℵ₀, ℵ₁, ComplexInfinity(), Inf, Inf32, Inf16, big(Inf)) negative = (-∞, -ComplexInfinity(), -Inf, -Inf32, -Inf16, -big(Inf)) - imaginary = (ComplexInfinity(0.5), complex(0.0, Inf)) + imaginary = (im*∞, complex(0.0, Inf)) others = (0, 1.5, -2, -1.5, 0.0, -0.0, NaN, NaN32, prevfloat(Inf), nextfloat(-Inf), nextfloat(0.0), prevfloat(-0.0), "∞", "-∞") @@ -448,6 +592,85 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test sorted[1] === -∞ && sorted[2] === 1.0 && sorted[3] === ∞ && isnan(sorted[4]) end + @testset "NaN arithmetic" begin + # the result is the package's own undefined value, as `Inf + ∞` is its own infinity + for nan in (NaN, NaN32, NaN16, big(NaN)), inf in (∞, +∞, -∞, ℵ₀) + for op in (+, -, *, div, fld, cld) + @test op(nan, inf) ≡ op(inf, nan) ≡ NotANumber() + end + # `mod(inf, x)` discards `x`, so only one order is needed + @test mod(nan, inf) ≡ NotANumber() + end + for nan in (NaN, NaN32, NaN16, big(NaN)), inf in (ComplexInfinity(), -ComplexInfinity()) + # arithmetic is defined for a complex operand, so the undefined result is complex + for op in (+, -, *) + @test op(nan, inf) ≡ op(inf, nan) ≡ complex(NotANumber(), NotANumber()) + end + # `Base` defines no integer operation for a `Complex`, and neither do we + for op in (div, fld, cld, mod, rem) + @test_throws MethodError op(nan, inf) + end + end + for nan in (NaN, NaN32, NaN16, big(NaN)), inf in (+∞, -∞) + @test inf^nan ≡ NotANumber() + end + end + + @testset "NotANumber" begin + nan = NotANumber() + # every operand it can meet, itself included + reals = (nan, 0, 1.5, ∞, +∞, -∞, ℵ₀, NaN, NaN32) + complexes = (ComplexInfinity(), (1+im)*∞, complex(1.0, 2.0), complex(true, false)) + operands = (reals..., complexes...) + @test isnan(nan) && !isinf(nan) && !isfinite(nan) && !iszero(nan) && !isone(nan) && !signbit(nan) + @test !isinteger(nan) + # a `NaN` of any real type is real, and this is the type-independent one + @test isreal(nan) + for f in (round, floor, ceil, trunc) + @test f(nan) ≡ f(nan; digits=2) ≡ nan + end + for r in (RoundNearest, RoundUp, RoundDown, RoundToZero) + @test round(nan, r) ≡ round(nan, r; digits=2) ≡ nan + end + + # a numeric comparison is false in every direction, against itself included + for op in (==, <, ≤, >, ≥, isapprox), x in operands + @test !op(nan, x) && !op(x, nan) + end + + # `isequal` and `hash` still identify it, as they do for `NaN` + @test isequal(nan, nan) && isequal(nan, NaN) && isequal(NaN32, nan) + @test hash(nan) == hash(NaN) + + # the sort order puts it last, alongside `NaN` + @test sort([1.0, nan, ∞, -∞])[end] ≡ nan + for x in operands + @test !isless(nan, x) && isless(x, nan) == !isnan(x) + end + + # it converts to the `NaN` of whichever float type is asked for + @test Float64(nan) ≡ float(nan) ≡ NaN + @test Float32(nan) ≡ NaN32 && Float16(nan) ≡ NaN16 + @test isnan(BigFloat(nan)) + + # anything computed from it is undefined again + for op in (+, -, *, /, ^, div, fld, cld, mod, rem, min, max), x in reals + @test op(nan, x) ≡ op(x, nan) ≡ nan + end + # a complex operand makes the undefined result complex, as it does over the floats + for op in (+, -, *, /, ^, div, fld, cld, mod, rem, min, max), x in complexes + @test op(nan, x) ≡ op(x, nan) ≡ complex(nan, nan) + end + for x in reals + @test divrem(nan, x) ≡ divrem(x, nan) ≡ (nan, nan) + end + for x in complexes + @test divrem(nan, x) ≡ divrem(x, nan) ≡ (complex(nan, nan), complex(nan, nan)) + end + @test nan^(1//2) ≡ (1//2)^nan ≡ ℯ^nan ≡ nan + @test -nan ≡ +nan ≡ abs(nan) ≡ inv(nan) ≡ sign(nan) ≡ conj(nan) ≡ nan + end + @testset "ordinary values" begin for inf in (∞, +∞, ℵ₀) @test 1.0 < inf && !(inf < 1.0) && 1.0 ≤ inf && inf ≥ 1.0 @@ -460,6 +683,20 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], @test max(-∞, ∞) === ∞ && min(-∞, ∞) === -∞ end + @testset "against the floats" begin + values = (0, 1, -2, 1.5, -1.5, 0.0, -0.0, NaN, NaN32, Inf, -Inf, + prevfloat(Inf), nextfloat(-Inf), nextfloat(0.0)) + for x in values, (inf, flt) in ((∞, Inf), (+∞, Inf), (-∞, -Inf), (ℵ₀, Inf)) + for op in (<, ≤, >, ≥, ==, isless, isequal) + @test op(x, inf) == op(x, flt) + @test op(inf, x) == op(flt, x) + end + for op in (max, min) + @test isequal(op(x, inf), op(x, flt)) && isequal(op(inf, x), op(flt, x)) + end + end + end + @testset "parsing" begin @test tryparse(NegativeInfinity, "-∞") == NegativeInfinity() @test tryparse(NegativeInfinity, " - ∞ ") == NegativeInfinity() diff --git a/test/test_ambiguity.jl b/test/test_ambiguity.jl index 03e2119..b8e0a82 100644 --- a/test/test_ambiguity.jl +++ b/test/test_ambiguity.jl @@ -5,17 +5,22 @@ @test_throws MethodError RealInfinity(Base.TwicePrecision(1.0)) @test_throws MethodError RealInfinity(im) - @test ComplexInfinity{Float64}(Base.TwicePrecision(1.0)) == ComplexInfinity(1) + @test ComplexInfinity(Base.TwicePrecision(1.0)) ≡ ComplexInfinity(-∞) @test_throws MethodError ComplexInfinity(im) - for inf in (∞,+∞,ℵ₀,ComplexInfinity()) + for inf in (∞,+∞,ℵ₀) @test mod(inf, 1//2) ≡ NotANumber() @test mod(1//2, inf) ≡ 1//2 @test fld(1//2, inf) == 0 @test cld(1//2, inf) == 1 @test div(1//2, inf) == 0 - @test fld(inf, 1//2) ≡ cld(inf, 1//2) ≡ div(inf, 1//2) ≡ inf - @test fld(inf, ∞) ≡ fld(inf, +∞) ≡ fld(inf, ℵ₀) ≡ fld(inf, ComplexInfinity()) ≡ NotANumber() + @test fld(inf, 1//2) ≡ cld(inf, 1//2) ≡ div(inf, 1//2) == inf + @test fld(inf, ∞) ≡ fld(inf, +∞) ≡ fld(inf, ℵ₀) ≡ NotANumber() + end + # `Base` defines no integer operation for a `Complex`, so neither does a `ComplexInfinity` take one + for op in (mod, fld, cld, div) + @test_throws MethodError op(ComplexInfinity(), 1//2) + @test_throws MethodError op(1//2, ComplexInfinity()) end @testset "rational power" begin diff --git a/test/test_cardinality.jl b/test/test_cardinality.jl index 16fb680..db7a516 100644 --- a/test/test_cardinality.jl +++ b/test/test_cardinality.jl @@ -130,7 +130,7 @@ Base.getindex(::InfVector, ::InfiniteCardinal{0}) = 42 @test 5 + ℵ₀ ≡ ℵ₀ + 5 ≡ ℵ₀ @test ℵ₀ - 5 ≡ ℵ₀ @test 5 - ℵ₀ ≡ -∞ - @test_throws ArgumentError ℵ₀ - ℵ₀ + @test ℵ₀ - ℵ₀ ≡ checked_sub(ℵ₀, ℵ₀) ≡ NotANumber() @test -ℵ₀ ≡ -∞ @test *(ℵ₀) ≡ ℵ₀