diff --git a/Project.toml b/Project.toml index fde6425..e1b5d36 100644 --- a/Project.toml +++ b/Project.toml @@ -8,10 +8,12 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] +Compat = "3.12" julia = "1" [extras] +Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Compat", "Test"] diff --git a/src/infextendedreal/arithmetic.jl b/src/infextendedreal/arithmetic.jl index bf471ed..f48d047 100644 --- a/src/infextendedreal/arithmetic.jl +++ b/src/infextendedreal/arithmetic.jl @@ -10,7 +10,7 @@ Base.:-(x::T) where {T<:InfExtendedReal} = T(-x.val) Base.:+(x::T, y::T) where {T<:InfExtendedReal} = isinf(x) ? isinf(y) ? T(x.val+y.val) : x : isinf(y) ? y : T(x.val+y.val) -Base.:-(x::T, y::T) where {T<:InfExtendedReal} = isinf(x) ? isinf(y) ? T(x.val - y.val) : x : isinf(y) ? -y : T(x.val-y.val) +Base.:-(x::T, y::T) where {T<:InfExtendedReal} = isinf(x) ? isinf(y) ? T(x.val-y.val) : x : isinf(y) ? -y : T(x.val-y.val) Base.:*(x::T, y::T) where {T<:InfExtendedReal} = if isinf(x) diff --git a/src/infextendedreal/base.jl b/src/infextendedreal/base.jl index 379125b..bfaa9c5 100644 --- a/src/infextendedreal/base.jl +++ b/src/infextendedreal/base.jl @@ -4,9 +4,22 @@ The type `T` extended with positive and negative infinity. """ struct InfExtendedReal{T<:Real} <: Real - val :: Union{T, Infinite} - InfExtendedReal{T}(x::Infinite) where {T<:Real} = new(x) - InfExtendedReal{T}(x::T) where {T<:Real} = new(x) + flag :: InfFlag + finitevalue :: T + + InfExtendedReal{T}(x::T) where {T<:Real} = new(FINITE, x) + InfExtendedReal{T}(x::Infinite) where {T<:Real} = new(x==PosInf ? POSINF : NEGINF) +end + +# Since InfExtendedReal is a subtype of Real, and Infinite is also a subtype of real, +# we can just use `x.val` to get either the finite value, or the infinite value. This will +# make arithmetic much simpler. +function Base.getproperty(x::InfExtendedReal, s::Symbol) + if s === :val + return isinf(x) ? (isneginf(x) ? -∞ : ∞) : x.finitevalue + else + return getfield(x, s) + end end InfExtendedReal{T}(x::Real) where {T<:Real} = InfExtendedReal{T}(isinf(x) ? convert(Infinite, x) : convert(T, x)) @@ -30,5 +43,5 @@ Converts `x` to a `InfExtendedReal(typeof(x))`. Utils.posinf(::Type{T}) where {T<:InfExtendedReal} = T(PosInf) Utils.neginf(::Type{T}) where {T<:InfExtendedReal} = T(NegInf) -Utils.isposinf(x::InfExtendedReal) = isposinf(x.val) -Utils.isneginf(x::InfExtendedReal) = isneginf(x.val) +Utils.isposinf(x::InfExtendedReal) = x.flag == POSINF || isposinf(x.finitevalue) +Utils.isneginf(x::InfExtendedReal) = x.flag == NEGINF || isneginf(x.finitevalue) diff --git a/src/infextendedreal/comparison.jl b/src/infextendedreal/comparison.jl index e88da36..363b671 100644 --- a/src/infextendedreal/comparison.jl +++ b/src/infextendedreal/comparison.jl @@ -1,6 +1,6 @@ -Base.isfinite(x::InfExtendedReal) = isfinite(x.val) +Base.isfinite(x::InfExtendedReal) = x.flag == FINITE && isfinite(x.finitevalue) -Base.isinf(x::InfExtendedReal) = isinf(x.val) +Base.isinf(x::InfExtendedReal) = isposinf(x) || isneginf(x) Base.:(==)(x::InfExtendedReal, y::InfExtendedReal) = isinf(x)==isinf(y) && x.val==y.val diff --git a/src/infextendedreal/io.jl b/src/infextendedreal/io.jl index 8a46dc7..99dc6c5 100644 --- a/src/infextendedreal/io.jl +++ b/src/infextendedreal/io.jl @@ -1 +1,10 @@ -Base.show(io::IO, x::InfExtendedReal) = show(io, x.val) +function Base.show(io::IO, x::T) where {T<:InfExtendedReal} + value = x.val + if get(io, :compact, false) + print(io, value) + else + print(io, "$T(") + show(io, value) + print(io, ")") + end +end diff --git a/test/infextendedreal.jl b/test/infextendedreal.jl index 1bcc629..e145806 100644 --- a/test/infextendedreal.jl +++ b/test/infextendedreal.jl @@ -1,77 +1,152 @@ @testset "InfExtendedRealReal" begin - @testset "base" begin - @test InfExtendedReal(Int) === InfExtendedReal{Int} - @test InfExtendedReal(Float64) === Float64 - @test InfExtendedReal(Rational{Int}) === Rational{Int} - @test InfExtendedReal(10) === InfExtendedReal{Int}(10) - @test InfExtendedReal(10.0) === 10.0 - @test InfExtendedReal(2//3) === 2//3 - @inferred InfExtendedReal(Int) - @inferred InfExtendedReal(Float64) - @inferred InfExtendedReal(Rational{Int}) - @inferred InfExtendedReal(10) - @inferred InfExtendedReal(1.2) - @inferred InfExtendedReal(2//3) - end - - @testset "arithmetic" begin - @inferred -∞ - @inferred 2 + ∞ - @inferred ∞ + 2.3 - @test_throws InfMinusInfError ∞+(-∞) - @inferred 10 - ∞ - @inferred 10.0 - ∞ - @test_throws InfMinusInfError ∞-∞ - @inferred 2 * ∞ - @inferred ∞ * 1.0 - @test_throws DivideError ∞ * 0 - @inferred 1 / ∞ - @inferred 1.2 / ∞ - @inferred -1 / -∞ - @inferred ∞ / 3 - @inferred 0 / ∞ - @test typemin(InfExtendedReal{Int}) === InfExtendedReal{Int}(-∞) - @test typemax(InfExtendedReal{Int}) === InfExtendedReal{Int}(∞) - end - - @testset "comparisons" begin - @test ∞ == ∞ - @test ∞ == Inf - @test InfExtendedReal(5) == 5 - @test InfExtendedReal(7) == 7.0 - @test InfExtendedReal(4) != InfExtendedReal(1) - @test hash(∞) == hash(Inf) - @test hash(InfExtendedReal{Int}(∞)) == hash(Inf) - @test hash(InfExtendedReal(3)) == hash(3) - @test isinf(∞) - @test isinf(InfExtendedReal{Int}(∞)) - @test !isinf(InfExtendedReal(9)) - @test !isfinite(∞) - @test !isfinite(InfExtendedReal{Int}(∞)) - @test isfinite(InfExtendedReal(-4)) - @test ∞ ≤ ∞ - @test 1 ≤ ∞ - @test -∞ ≤ -∞ - @test !(∞ ≤ 0) - @test !signbit(∞) - @test signbit(-∞) - @test !signbit(InfExtendedReal{Int}(∞)) - @test signbit(InfExtendedReal{Int}(-∞)) - @test !signbit(InfExtendedReal(20)) - @test signbit(InfExtendedReal(-2)) - @test sign(∞) == 1 - @test sign(-∞) == -1 - @test sign(InfExtendedReal{Int}(∞)) == 1 - @test sign(InfExtendedReal{Int}(-∞)) == -1 - @test sign(InfExtendedReal(3)) == 1 - @test sign(InfExtendedReal(-99)) == -1 - @test sign(InfExtendedReal(0)) == 0 - @inferred sign(InfExtendedReal(2)) - @inferred sign(InfExtendedReal{Int32}(∞)) - end - - @testset "conversions" begin - @test convert(Infinite, InfExtendedReal{Int}(∞)) === ∞ - end + @testset "Base" begin + for (T, x) in ((Int, 1), (Float64, 1.0), (Rational{Int}, 2//3)) + @inferred InfExtendedReal(T) + @inferred InfExtendedReal(x) + @inferred InfExtendedReal{T}(x) + @inferred InfExtendedReal{T}(∞) + @inferred InfExtendedReal{T}(-∞) + end + # Specifically test that if a value can represent `Inf` it doesn't get wrapped in + # `InfExtendedReal` + @test InfExtendedReal(Int) === InfExtendedReal{Int} + @test InfExtendedReal(Float64) === Float64 + @test InfExtendedReal(Rational{Int}) === Rational{Int} + @test InfExtendedReal(10) === InfExtendedReal{Int}(10) + @test InfExtendedReal(10.0) === 10.0 + @test InfExtendedReal(2//3) === 2//3 + + @test InfExtendedReal{Int}(Inf) == InfExtendedReal{Int}(∞) + @test InfExtendedReal{Float64}(InfExtendedReal{Int}(10)) === + InfExtendedReal{Float64}(10.0) + @test InfExtendedReal{Int}(InfExtendedReal{Int}(1)) === InfExtendedReal{Int}(1) + + a = InfExtendedReal{Int}(1) + inf = InfExtendedReal{Int}(∞) + ninf = InfExtendedReal{Int}(-∞) + @test posinf(typeof(a)) == inf + @test neginf(typeof(a)) == ninf + @test !isposinf(a) + @test !isneginf(a) + @test isposinf(inf) + @test !isneginf(inf) + @test !isposinf(ninf) + @test isneginf(ninf) + end + + @testset "IO" begin + x = InfExtendedReal{Int64}(2) + i = InfExtendedReal{Int64}(∞) + + @test string(x) == "InfExtendedReal{Int64}(2)" + @test sprint(show, x, context=:compact=>true) == "2" + @test sprint(show, x) == string(x) + + @test string(i) == "InfExtendedReal{Int64}(∞)" + @test sprint(show, i, context=:compact=>true) == "∞" + @test sprint(show, i) == string(i) + end + + @testset "Conversion" begin + @test promote_rule(Infinite, Float64) === InfExtendedReal(Float64) + @test promote_rule(InfExtendedReal{Int64}, InfExtendedReal{Float64}) === Float64 + @test promote_rule(InfExtendedReal{Int32}, InfExtendedReal{Int64}) === + InfExtendedReal{Int64} + @test promote_rule(InfExtendedReal{Int64}, Float64) === Float64 + @test promote_rule(InfExtendedReal{Int64}, Infinite) === InfExtendedReal{Int64} + + @test convert(Int64, InfExtendedReal{Float64}(2.0)) == 2 + @test convert(Infinite, InfExtendedReal{Int}(∞)) === ∞ + @test convert(InfExtendedReal{Int64}, 2.0) === InfExtendedReal{Int64}(2.0) + @test convert(InfExtendedReal{Int64}, InfExtendedReal{Float64}(2.0)) === InfExtendedReal{Int64}(2.0) + @test convert(InfExtendedReal{Int64}, ∞) == InfExtendedReal{Int64}(∞) + + @test Float64(InfExtendedReal{Int64}(2)) === 2.0 + + @test widen(InfExtendedReal{Int32}) === InfExtendedReal{Int64} + @test big(InfExtendedReal{Int}) === InfExtendedReal{BigInt} + @test big(InfExtendedReal{Int64}(2)) == InfExtendedReal{BigInt}(2) + + @test float(InfExtendedReal{Int}) === Float64 + @test float(InfExtendedReal) === Float64 + end + + @testset "comparisons" begin + @test !isfinite(InfExtendedReal{Int}(∞)) + @test isfinite(InfExtendedReal(-4)) + @test !isfinite(InfExtendedReal{Float64}(Inf)) + + @test isinf(InfExtendedReal{Int}(∞)) + @test !isinf(InfExtendedReal(9)) + @test isinf(InfExtendedReal{Float64}(Inf)) + + @test InfExtendedReal(5) == 5 + @test InfExtendedReal(7) == 7.0 + @test InfExtendedReal(4) != InfExtendedReal(1) + + @test hash(InfExtendedReal(3)) == hash(3) + + @test InfExtendedReal(2) < InfExtendedReal{Int}(∞) + @test InfExtendedReal{Int}(-∞) < InfExtendedReal(2) + @test InfExtendedReal(2) <= InfExtendedReal(4) + + @test !signbit(InfExtendedReal{Int}(∞)) + @test signbit(InfExtendedReal{Int}(-∞)) + @test !signbit(InfExtendedReal(20)) + @test signbit(InfExtendedReal(-2)) + + @test sign(∞) == 1 + @test sign(-∞) == -1 + @test sign(InfExtendedReal{Int}(∞)) == 1 + @test sign(InfExtendedReal{Int}(-∞)) == -1 + @test sign(InfExtendedReal(3)) == 1 + @test sign(InfExtendedReal(-99)) == -1 + @test sign(InfExtendedReal(0)) == 0 + + @inferred sign(InfExtendedReal(2)) + @inferred sign(InfExtendedReal{Int32}(∞)) + + @test isapprox(InfExtendedReal{Float64}(2.000000001), InfExtendedReal{Float64}(2.0000000004)) + end + + @testset "arithmetic" begin + @inferred -∞ + @inferred InfExtendedReal 2 + ∞ + @inferred ∞ + 2.3 + @test_throws InfMinusInfError ∞+(-∞) + @inferred InfExtendedReal 10 - ∞ + @inferred InfExtendedReal 10.0 - ∞ + @test_throws InfMinusInfError ∞-∞ + @inferred InfExtendedReal 2 * ∞ + @inferred ∞ * 1.0 + @test_throws DivideError ∞ * 0 + @inferred Float64 1 / ∞ + @inferred Float64 1.2 / ∞ + @inferred Float64 -1 / -∞ + @inferred Float64 ∞ / 3 + @inferred Float64 0 / ∞ + + @test typemin(InfExtendedReal{Int64}) == InfExtendedReal{Int64}(-∞) + @test typemax(InfExtendedReal{Int64}) == InfExtendedReal{Int64}(∞) + + @test +InfExtendedReal(2) == InfExtendedReal(2) + @test -InfExtendedReal(2) == InfExtendedReal(-2) + + @test InfExtendedReal(2) + InfExtendedReal(3) == InfExtendedReal(5) + @test InfExtendedReal(3) - InfExtendedReal(2) == InfExtendedReal(1) + @test InfExtendedReal(2) * InfExtendedReal(3) == InfExtendedReal(6) + @test InfExtendedReal{Int}(∞) * InfExtendedReal{Int}(∞) == InfExtendedReal{Int}(∞) + @test_throws DivideError InfExtendedReal(0) * InfExtendedReal{Int}(∞) + @test InfExtendedReal(10) / InfExtendedReal(5) == InfExtendedReal(2) + @test_throws DivideError InfExtendedReal{Int}(∞) / InfExtendedReal{Int}(∞) + @test InfExtendedReal(10) / InfExtendedReal{Int}(∞) == InfExtendedReal(0) + + @test abs(InfExtendedReal(-5)) == InfExtendedReal(5) + + @test InfExtendedReal(1) // InfExtendedReal(0) == InfExtendedReal(1//0) + @test_throws DivideError InfExtendedReal{Int}(∞) // InfExtendedReal{Int}(∞) + @test InfExtendedReal(1) // 0 == InfExtendedReal(1//0) + @test 1 // InfExtendedReal(0) == InfExtendedReal(1//0) + end end diff --git a/test/infextendedtime.jl b/test/infextendedtime.jl index 3d5d509..1baadf2 100644 --- a/test/infextendedtime.jl +++ b/test/infextendedtime.jl @@ -76,11 +76,11 @@ test_time = Time(1, 1, 1, 1) end @testset "Conversion" begin - @test promote_type(InfExtendedTime{DateTime}, InfExtendedTime{Date}) == + @test promote_rule(InfExtendedTime{DateTime}, InfExtendedTime{Date}) == InfExtendedTime{DateTime} - @test promote_type(InfExtendedTime{DateTime}, Date) == InfExtendedTime{DateTime} - @test promote_type(InfExtendedTime{Date}, Infinite) == InfExtendedTime{Date} - @test promote_type(Infinite, Date) == InfExtendedTime{Date} + @test promote_rule(InfExtendedTime{DateTime}, Date) == InfExtendedTime{DateTime} + @test promote_rule(InfExtendedTime{Date}, Infinite) == InfExtendedTime{Date} + @test promote_rule(Infinite, Date) == InfExtendedTime{Date} @test convert(InfExtendedTime{Date}, test_datetime) == InfExtendedTime{Date}(test_date) @@ -149,6 +149,7 @@ test_time = Time(1, 1, 1, 1) @test ninf < ∞ @test inf <= ∞ @test ninf <= -∞ + @test -∞ <= ninf end @testset "Arithmetic" begin @@ -156,7 +157,9 @@ test_time = Time(1, 1, 1, 1) @test typemax(InfExtendedTime{Date}) == InfExtendedTime{Date}(∞) @test InfExtendedTime(test_date) + Day(1) == InfExtendedTime(test_date + Day(1)) + @test Day(1) + InfExtendedTime(test_date) == InfExtendedTime(test_date + Day(1)) @test InfExtendedTime(test_date) - Day(1) == InfExtendedTime(test_date - Day(1)) + @test Day(1) - InfExtendedTime(test_date) == InfExtendedTime(test_date - Day(1)) @test InfExtendedTime{Date}(∞) + Day(1) == InfExtendedTime{Date}(∞) @test InfExtendedTime{Date}(∞) - Day(1) == InfExtendedTime{Date}(∞) @test InfExtendedTime{Date}(-∞) + Day(1) == InfExtendedTime{Date}(-∞) diff --git a/test/infinite.jl b/test/infinite.jl new file mode 100644 index 0000000..996e69a --- /dev/null +++ b/test/infinite.jl @@ -0,0 +1,105 @@ +@testset "Infinite" begin + @testset "Base" begin + @test_throws MethodError Infinite() + @inferred Infinite(true) + @inferred Infinite(false) + + @test Infinite(∞) == ∞ + + buf = IOBuffer() + showerror(buf, InfMinusInfError()) + msg = String(take!(buf)) + @test msg == "∞-∞ is undefined" + end + + @testset "IO" begin + inf = Infinite(false) + ninf = Infinite(true) + + @test string(inf) == "∞" + @test sprint(show, inf, context=:compact=>true) == "∞" + @test sprint(show, inf) == string(inf) + + @test string(ninf) == "-∞" + @test sprint(show, ninf, context=:compact=>true) == "-∞" + @test sprint(show, ninf) == string(ninf) + end + + @testset "Conversion" begin + @test_throws InexactError convert(Int, ∞) + @test convert(Float64, ∞) == Inf + @test convert(Rational{Int}, ∞) == 1//0 + + @test_throws InexactError convert(Infinite, 2) + @test convert(Infinite, Inf) == ∞ + @test convert(Infinite, 1//0) == ∞ + + @test convert(Infinite, ∞) == ∞ + + @test Float64(∞) == Inf + + @test one(Infinite) == Infinity.UnknownReal + @test zero(Infinite) == Infinity.UnknownReal + @test float(Infinite) == float(Int) + @test float(Infinity.UnknownReal) == float(Int) + end + + @testset "Comparison" begin + x = ∞ + @test !isfinite(x) + @test !isfinite(-x) + @test isinf(x) + @test isinf(-x) + @test x == x + @test x != -x + @test hash(x) != hash(-x) + @test -x < x + @test x > -x + @test x <= x + @test -x >= -x + @test signbit(-x) + @test !signbit(x) + @test sign(x) == 1 + @test sign(-x) == -1 + @test isapprox(x, x) + @test !isapprox(x, -x) + end + + @testset "Arithmetic" begin + x = ∞ + @test typemin(Infinite) == -x + @test typemax(Infinite) == x + + @test +x == x + @test -x == -x + @test -(-x) == x + + @test_throws InfMinusInfError x - x + @test x + x == x + @test -x + -x == -x + @test -x - x == -x + + @test x * x == x + @test x * -x == -x + @test -x * x == -x + @test -x * -x == x + + @test_throws DivideError x / x + + @test abs(x) == x + @test abs(-x) == x + + @test_throws DivideError x // x + @test x // 2 == 1//0 + @test 2 // x == 0//1 + end + + @testset "Rand" begin + @test typeof(rand(Infinite)) === Infinite + + Random.seed!(1) + @test rand(Infinite) == ∞ + @test rand(Infinite) == ∞ + @test rand(Infinite) == -∞ + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 118b52c..1723f23 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,22 +1,13 @@ -using Infinity, Infinity.Utils, Test, Dates +using Compat +using Dates +using Infinity +using Infinity.Utils +using Random +using Test @testset "Infinity" begin - @testset "utils" begin - @test !hasinf(Int) - @test hasinf(Float64) - @test hasinf(Rational{Int}) - end - - @testset "Infinite" begin - @testset "identity" begin - @test Infinite(∞) == ∞ - end - - @testset "rand" begin - @test typeof(rand(Infinite)) === Infinite - end - end - - include("infextendedreal.jl") - include("infextendedtime.jl") + include("utils.jl") + include("infinite.jl") + include("infextendedreal.jl") + include("infextendedtime.jl") end diff --git a/test/utils.jl b/test/utils.jl new file mode 100644 index 0000000..5be3afd --- /dev/null +++ b/test/utils.jl @@ -0,0 +1,29 @@ +@testset "Utils" begin + @test isnothing(posinf(Int)) + @test posinf(Float64) == Inf + @test posinf(Rational{Int}) == Inf + + @test isnothing(neginf(Int)) + @test neginf(Float64) == -Inf + @test neginf(Rational{Int}) == -Inf + + @test !hasposinf(Int) + @test hasposinf(Float64) + @test hasposinf(Rational{Int}) + + @test !hasneginf(Int) + @test hasneginf(Float64) + @test hasneginf(Rational{Int}) + + @test !hasinf(Int) + @test hasinf(Float64) + @test hasinf(Rational{Int}) + + @test !isposinf(1) + @test isposinf(Inf) + @test !isposinf(-Inf) + + @test !isneginf(1) + @test !isneginf(Inf) + @test isneginf(-Inf) +end