diff --git a/src/algebra.jl b/src/algebra.jl index 669cc4a..7cdd946 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -17,9 +17,14 @@ -(::InfiniteCardinal) = -∞ +# The direction of an infinity in half turns, which for a real is its sign bit. +@inline _halfturns(x) = signbit(x) +@inline _halfturns(x::Complex) = angle(x)/π + + # addition @inline toinf(x) = RealInfinity(signbit(x)) -@inline toinf(x::Complex) = ComplexInfinity(angle(x)) +@inline toinf(x::Complex) = ComplexInfinity(_halfturns(x)) @inline toinf(x::ComplexInfinity) = x @inline _infadd(x, y) = angle(x) == angle(y) ? y : throw(ArgumentError("Angles must be the same to add ∞")) @@ -44,13 +49,10 @@ # multiplication -@inline _sb(x) = signbit(x) -@inline _sb(x::Complex) = angle(x)/π # overloading `signbit` causes type piracy - -@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(_halfturns(x) ⊻ _halfturns(y)) +@inline __mul(x, y::ComplexInfinity) = ComplexInfinity(_halfturns(x) + _halfturns(y)) +@inline __mul(x, y::ComplexInfinity{Bool}) = ComplexInfinity(_halfturns(x) ⊻ _halfturns(y)) +@inline __mul(x::Complex, y::ComplexInfinity{Bool}) = ComplexInfinity(_halfturns(x) + _halfturns(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)...) diff --git a/test/runtests.jl b/test/runtests.jl index a1d5069..e51b1e5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -283,12 +283,36 @@ Base.iterate(s::CharString, i::Integer=1) = i ≤ length(s.chars) ? (s.chars[i], ComplexInfinity() * ∞ ≡ ComplexInfinity() * RealInfinity() ≡ ComplexInfinity() @test 2.0im*∞ ≡ ∞*2.0im ≡ 2.0im * RealInfinity() ≡ RealInfinity() * 2.0im ≡ ComplexInfinity(1/2) + # On an axis `angle(x)/π` is exact, `angle` giving exactly `0`, `±π/2` or `π` and halving being exact. + for (z, s, w) in ((2.0+0.0im, 0.0, complex(Inf, 0.0)), (2.0im, 0.5, complex(0.0, Inf)), + (-2.0+0.0im, 1.0, complex(-Inf, 0.0)), (-2.0im, -0.5, complex(0.0, -Inf))) + @test z*∞ ≡ ComplexInfinity(s) + @test z*∞ == w + end @test 2ComplexInfinity() ≡ ComplexInfinity()*2 ≡ ComplexInfinity() - @test exp(im*π/4)*∞ == Inf+im*Inf + # Off the axes it is not exact in general: `angle(exp(im*π/8))/π` gives 0.12500000000000003. + for θ in (π/8, π/4, 0.3, -2.4) + @test angle(exp(im*θ)*∞) ≈ angle(∞*exp(im*θ)) ≈ θ + end + # The direction is the one the operand actually has, so rescaling keeps it only while it is exact. + @test 4*(0.3+0.1im)*∞ ≡ (0.3+0.1im)*∞ + @test 3*(0.3+0.1im)*∞ ≢ (0.3+0.1im)*∞ @test exp(im*π/4)+∞ == ∞ @test Inf + im + ∞ ≡ ComplexInfinity() + # An infinite summand reaches `_infadd` through `toinf`, which has to answer in half turns. + @test complex(Inf, 0.0) + ∞ ≡ ComplexInfinity() + @test complex(-Inf, 0.0) + (-∞) ≡ -ComplexInfinity() + @test complex(0.0, Inf) + ComplexInfinity(1/2) ≡ ComplexInfinity(1/2) + @test complex(0.0, -Inf) + ComplexInfinity(-1/2) ≡ ComplexInfinity(-1/2) + @test_throws ArgumentError complex(0.0, Inf) + ∞ + + # Two infinite parts are the only way an infinite `Complex` points off the axes. + for (x, y, s) in ((Inf, Inf, 1/4), (-Inf, Inf, 3/4), (-Inf, -Inf, -3/4), (Inf, -Inf, -1/4)) + @test complex(x, y) + ComplexInfinity(s) ≡ ComplexInfinity(s) + end + @test Inf == ComplexInfinity() @test ComplexInfinity() == Inf