diff --git a/Project.toml b/Project.toml index bbcbc2e..f8a0f4b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "MultiplesOfPi" uuid = "b749d01f-fee9-4313-9f11-89ddf7ea9d58" authors = ["Jishnu Bhattacharya", "Center for Space Science", "New York University Abu Dhabi"] -version = "0.1.3" +version = "0.2.0" [compat] julia = "1" diff --git a/README.md b/README.md index 7f60f54..4ba91db 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ This package introduces the type `PiTimes` that automatically uses the functions The number `π` is represented as an `Irrational` type in julia, and may be computed to an arbitrary degree of precision. In normal course of events it is converted to a float when it encounters another number, for example `2π` is computed by converting both `2` and `π` to floats and subsequently carrying out a floating-point multiplication. This is lossy, as both `2` and `π` may be represented with arbitrary precision. This package delays the conversion of the `π` to a float, treating it as a common factor in algebraic simplifications. This limits floating-point inaccuracies, especially if the terms multiplying `π` are exactly representable in binary. As an added advantage, it uses `sinpi` and `cospi` wherever possible to avoid having to convert `π` to a float altogether. -The concept isn't unknown to julia as such, having been [discussed in 2013](https://github.com/JuliaLang/julia/pull/4112#issuecomment-22961778), but I don't know of an implementation. - # Examples ## Arithmetic diff --git a/src/MultiplesOfPi.jl b/src/MultiplesOfPi.jl index 0a18c6c..31ce306 100644 --- a/src/MultiplesOfPi.jl +++ b/src/MultiplesOfPi.jl @@ -28,22 +28,17 @@ Base.:(==)(p::PiTimes,::Irrational{:π}) = isone(p.x) Base.:(==)(::Irrational{:π},p::PiTimes) = isone(p.x) for T in (:BigFloat,:Float64,:Float32,:Float16) - eval(quote - Base.$T(p::PiTimes) = π*$T(p.x) - end) + @eval Base.$T(p::PiTimes) = π*$T(p.x) end for f in (:iszero,:isfinite,:isnan) - eval(quote - Base.$f(p::PiTimes) = $f(p.x) - end) + @eval Base.$f(p::PiTimes) = $f(p.x) end # Unfortunately Irrational numbers do not have a multiplicative identity of the same type, # so we make do with something that works -# NOTE: This will be changed in the next minor release to one(::PiTimes) = true Base.one(::PiTimes{T}) where {T} = one(PiTimes{T}) -Base.one(::Type{PiTimes{T}}) where {T} = one(T) +Base.one(::Type{PiTimes{T}}) where {T} = true Base.zero(::PiTimes{T}) where {T} = zero(PiTimes{T}) Base.zero(::Type{PiTimes{T}}) where {T} = PiTimes(zero(T)) @@ -104,4 +99,27 @@ end Base.:(//)(p::PiTimes,n) = PiTimes(p.x//n) +# Conversion and promotion + +for t in (Int8, Int16, Int32, Int64, Int128, Bool, UInt8, UInt16, UInt32, UInt64, UInt128) + @eval Base.promote_rule(::Type{PiTimes{Float16}}, ::Type{PiTimes{$t}}) = PiTimes{Float16} + @eval Base.promote_rule(::Type{PiTimes{$t}},::Type{PiTimes{Float16}}) = PiTimes{Float16} +end + +for t1 in (Float32, Float64) + for t2 in (Int8, Int16, Int32, Int64, Bool, UInt8, UInt16, UInt32, UInt64) + @eval begin + Base.promote_rule(::Type{PiTimes{$t1}}, ::Type{PiTimes{$t2}}) = PiTimes{$t1} + Base.promote_rule(::Type{PiTimes{$t2}}, ::Type{PiTimes{$t1}}) = PiTimes{$t1} + end + end +end + +Base.promote_rule(::Type{PiTimes{T}}, ::Type{Irrational{:π}}) where {T} = PiTimes{T} +Base.promote_rule(::Type{Irrational{:π}}, ::Type{PiTimes{T}}) where {T} = PiTimes{T} + +Base.convert(::Type{PiTimes},x::Real) = PiTimes(x/π) +Base.convert(::Type{PiTimes{T}},x::Real) where {T} = PiTimes{T}(x/π) +Base.convert(::Type{PiTimes{T}},p::PiTimes) where {T} = PiTimes{T}(p.x) + end # module diff --git a/test/runtests.jl b/test/runtests.jl index dc85d69..c6bde9a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,15 +30,11 @@ end @testset "zero and one" begin @testset "one" begin - for T in (Float16,Float32,Float64,Int8,Int16,Int32,Int64,Int128) - @test one(PiTimes{T}) === one(T) - @test one(PiTimes{T}(0)) === one(T) - end - for T in (BigInt,BigFloat) - @test one(PiTimes{T}) == one(T) - @test one(PiTimes{T}(0)) == one(T) + for T in (Float16,Float32,Float64,Int8,Int16,Int32,Int64,Int128,BigInt,BigFloat) + @test one(PiTimes{T}) === true + @test one(PiTimes{T}(0)) === true end - @test one(Pi) == 1 + @test one(Pi) === true end @testset "zero" begin @test zero(Pi) === PiTimes(0) == 0 @@ -57,16 +53,60 @@ end end @testset "conversion" begin - @test convert(Float64,PiTimes(1)) ≈ Float64(1π) - @test Float64(PiTimes(1)) ≈ Float64(1)*π - @test convert(Float64,PiTimes(2)) ≈ Float64(2π) - @test Float64(PiTimes(2)) ≈ Float64(2)*π - @test convert(BigFloat,PiTimes(1)) ≈ BigFloat(1)*π - @test BigFloat(PiTimes(1)) ≈ BigFloat(1)*π - @test convert(Float32,PiTimes(1)) ≈ Float32(1)*π - @test Float32(PiTimes(1)) ≈ Float32(1)*π - @test convert(Float16,PiTimes(1)) ≈ Float16(1)*π - @test Float16(PiTimes(1)) ≈ Float16(1)*π + @testset "promote_rule" begin + for t in (Int8, Int16, Int32, Int64, Int128, Bool, UInt8, UInt16, UInt32, UInt64, UInt128) + @test promote_rule(PiTimes{Float16},PiTimes{t}) === PiTimes{Float16} + @test promote_rule(PiTimes{t},PiTimes{Float16}) === PiTimes{Float16} + end + for t1 in (Float32, Float64) + for t2 in (Int8, Int16, Int32, Int64, Bool, UInt8, UInt16, UInt32, UInt64) + @test promote_rule(PiTimes{t1},PiTimes{t2}) === PiTimes{t1} + @test promote_rule(PiTimes{t2},PiTimes{t1}) === PiTimes{t1} + end + end + @test promote_rule(PiTimes{Int},Irrational{:π}) === PiTimes{Int} + @test promote_rule(PiTimes{Float64},Irrational{:π}) === PiTimes{Float64} + end + @testset "convert" begin + @testset "to float" begin + @test convert(Float64,PiTimes(1)) ≈ Float64(1π) + @test Float64(PiTimes(1)) ≈ Float64(1)*π + @test convert(Float64,PiTimes(2)) ≈ Float64(2π) + @test Float64(PiTimes(2)) ≈ Float64(2)*π + @test convert(BigFloat,PiTimes(1)) ≈ BigFloat(1)*π + @test BigFloat(PiTimes(1)) ≈ BigFloat(1)*π + @test convert(Float32,PiTimes(1)) ≈ Float32(1)*π + @test Float32(PiTimes(1)) ≈ Float32(1)*π + @test convert(Float16,PiTimes(1)) ≈ Float16(1)*π + @test Float16(PiTimes(1)) ≈ Float16(1)*π + end + @testset "from real" begin + @test convert(PiTimes{Int},π) === PiTimes(1) + @test convert(PiTimes{Float64},π) === PiTimes(1.0) + @test convert(PiTimes,π) === PiTimes(1.0) + + @test convert(PiTimes,2) === PiTimes(2/π) + for t in (Float16,Float32,Float64) + @test convert(PiTimes{t},2) === PiTimes{t}(2/π) + end + convert(PiTimes{BigFloat},2) === PiTimes{BigFloat}(2/π) + end + @testset "PiTimes" begin + for t in (Int8, Int16, Int32, Int64, Int128, Bool, UInt8, UInt16, UInt32, UInt64, UInt128) + @test convert(PiTimes{Float16},PiTimes{t}(0)) === PiTimes(Float16(0)) + end + for t1 in (Float32, Float64) + for t2 in (Int8, Int16, Int32, Int64, Bool, UInt8, UInt16, UInt32, UInt64) + @test convert(PiTimes{t1},PiTimes{t2}(0)) === PiTimes(t1(0)) + end + end + end + @testset "Complex" begin + @test Complex(Pi,Pi) == Pi + im*Pi + @test Complex(Pi,π) == Pi + im*Pi + @test Complex(π,Pi) == Pi + im*Pi + end + end end @testset "arithmetic" begin