diff --git a/Project.toml b/Project.toml index 389876d..4b97786 100644 --- a/Project.toml +++ b/Project.toml @@ -7,7 +7,8 @@ version = "0.1.1" julia = "1" [extras] +Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Test", "Compat"] diff --git a/src/IrrationalConstants.jl b/src/IrrationalConstants.jl index e9c2b27..79a20ba 100644 --- a/src/IrrationalConstants.jl +++ b/src/IrrationalConstants.jl @@ -29,5 +29,6 @@ export log4π # log(4π) include("stats.jl") +include("rules.jl") end # module diff --git a/src/rules.jl b/src/rules.jl new file mode 100644 index 0000000..69f6c9d --- /dev/null +++ b/src/rules.jl @@ -0,0 +1,68 @@ +## Square +let SQUARE_PAIRS = ( + (sqrt2, 2.0), + (sqrt3, 3.0), + (sqrtπ, π), + (sqrt2π, twoπ), + (sqrt4π, fourπ), + (sqrthalfπ, halfπ), + (invsqrt2, 0.5), + (invsqrtπ, invπ), + (invsqrt2π, inv2π), + ) + for (a,b) in SQUARE_PAIRS + # The output type can be Irrational, but we enforces Float64 for now. + Base.:(*)(::typeof(a), ::typeof(a)) = Float64(b) + Base.literal_pow(::typeof(^), ::typeof(a), ::Val{2}) = Float64(b) + end +end + +## Inverse +let INVERSE_PAIRS = ( + (π, invπ), + (twoπ, inv2π), + (twoinvπ, halfπ), + (quartπ, fourinvπ), + (fourπ, inv4π), + (sqrt2π, invsqrt2π), + (sqrt2, invsqrt2), + (sqrtπ, invsqrtπ), + ) + for (a,b) in INVERSE_PAIRS + if a !== π # Avoid type piracy + Base.inv(::typeof(a)) = Float64(b) + Base.literal_pow(::typeof(^), ::typeof(a), ::Val{-1}) = Float64(b) + end + Base.inv(::typeof(b)) = Float64(a) + Base.literal_pow(::typeof(^), ::typeof(b), ::Val{-1}) = Float64(a) + Base.:(*)(::typeof(a), ::typeof(b)) = 1.0 # This can be one(Irrational). + Base.:(*)(::typeof(b), ::typeof(a)) = 1.0 # This can be one(Irrational). + end +end + +## Triangular +Base.sin(::Irrational{:twoπ}) = 0.0 +Base.cos(::Irrational{:twoπ}) = 1.0 +Base.sincos(::Irrational{:twoπ}) = (0.0, 1.0) +Base.sin(::Irrational{:fourπ}) = 0.0 +Base.cos(::Irrational{:fourπ}) = 1.0 +Base.sincos(::Irrational{:fourπ}) = (0.0, 1.0) +Base.sin(::Irrational{:quartπ}) = Float64(invsqrt2) +Base.cos(::Irrational{:quartπ}) = Float64(invsqrt2) +Base.sincos(::Irrational{:quartπ}) = (Float64(invsqrt2), Float64(invsqrt2)) +Base.sin(::Irrational{:halfπ}) = 1.0 +Base.cos(::Irrational{:halfπ}) = 0.0 +Base.sincos(::Irrational{:halfπ}) = (1.0, 0.0) + +## Exponential +Base.exp(::Irrational{:loghalf}) = 0.5 +Base.exp(::Irrational{:logtwo}) = 2.0 +Base.exp(::Irrational{:logten}) = 10.0 +Base.exp(::Irrational{:logπ}) = Float64(π) +Base.exp(::Irrational{:log2π}) = Float64(twoπ) +Base.exp(::Irrational{:log4π}) = Float64(fourπ) + +## Logarithm +# Base.log(::Irrational{:π}) = logπ # Avoid type piracy +Base.log(::Irrational{:twoπ}) = Float64(log2π) +Base.log(::Irrational{:fourπ}) = Float64(log4π) diff --git a/test/runtests.jl b/test/runtests.jl index 0bab199..fbb8ba6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,42 +1,133 @@ using IrrationalConstants using Test +using Compat -@testset "k*pi" begin - @test isapprox(2*pi, twoπ) - @test isapprox(4*pi, fourπ) - @test isapprox(pi/2, halfπ) - @test isapprox(pi/4, quartπ) -end +const IRRATIONALS = ( + twoπ, + fourπ, + halfπ, + quartπ, + invπ, + twoinvπ, + fourinvπ, + inv2π, + inv4π, + sqrt2, + sqrt3, + sqrtπ, + sqrt2π, + sqrt4π, + sqrthalfπ, + invsqrt2, + invsqrtπ, + invsqrt2π, + loghalf, + logtwo, + logten, + logπ, + log2π, + log4π, +) -@testset "k/pi" begin - @test isapprox(1/pi, invπ) - @test isapprox(2/pi, twoinvπ) - @test isapprox(4/pi, fourinvπ) -end +const INVERSE_PAIRS = ( + (π, invπ), + (twoπ, inv2π), + (twoinvπ, halfπ), + (quartπ, fourinvπ), + (fourπ, inv4π), + (sqrt2π, invsqrt2π), + (sqrt2, invsqrt2), + (sqrtπ, invsqrtπ), +) + +function test_with_function(f, a::Irrational) + b = f(a) + @test b ≈ f(float(a)) atol=1e-14 + + # The output type can be Irrational, but we enforces Float64 for now. + # # If f(a) is approximately equal to a value in IRRATIONALS, f(a) should be Irrational. + # @test (b .≈ IRRATIONALS) == (b .=== IRRATIONALS) + @test b isa Float64 -@testset "1/(k*pi)" begin - @test isapprox(1/(2pi), inv2π) - @test isapprox(1/(4pi), inv4π) + # If f(a) is close to integer, it should be a integer. + if abs(b - round(b)) < 1e-14 + @test isinteger(b) + end end -@testset "sqrt" begin - @test isapprox(sqrt(2), sqrt2) - @test isapprox(sqrt(3), sqrt3) - @test isapprox(sqrt(pi), sqrtπ) - @test isapprox(sqrt(2pi), sqrt2π) - @test isapprox(sqrt(4pi), sqrt4π) - @test isapprox(sqrt(pi/2), sqrthalfπ) - @test isapprox(sqrt(1/2), invsqrt2) - @test isapprox(sqrt(1/(pi)), invsqrtπ) - @test isapprox(sqrt(1/(2pi)), invsqrt2π) +@testset "approximately equal" begin + @testset "k*pi" begin + @test isapprox(2*pi, twoπ) + @test isapprox(4*pi, fourπ) + @test isapprox(pi/2, halfπ) + @test isapprox(pi/4, quartπ) + end + + @testset "k/pi" begin + @test isapprox(1/pi, invπ) + @test isapprox(2/pi, twoinvπ) + @test isapprox(4/pi, fourinvπ) + end + + @testset "1/(k*pi)" begin + @test isapprox(1/(2pi), inv2π) + @test isapprox(1/(4pi), inv4π) + end + + @testset "sqrt" begin + @test isapprox(sqrt(2), sqrt2) + @test isapprox(sqrt(3), sqrt3) + @test isapprox(sqrt(pi), sqrtπ) + @test isapprox(sqrt(2pi), sqrt2π) + @test isapprox(sqrt(4pi), sqrt4π) + @test isapprox(sqrt(pi/2), sqrthalfπ) + @test isapprox(sqrt(1/2), invsqrt2) + @test isapprox(sqrt(1/(pi)), invsqrtπ) + @test isapprox(sqrt(1/(2pi)), invsqrt2π) + end + + @testset "log" begin + @test isapprox(log(1/2), loghalf) + @test isapprox(log(2), logtwo) + @test isapprox(log(10), logten) + @test isapprox(log(pi), logπ) + @test isapprox(log(2pi), log2π) + @test isapprox(log(4pi), log4π) + end end -@testset "log" begin - @test isapprox(log(1/2), loghalf) - @test isapprox(log(2), logtwo) - @test isapprox(log(10), logten) - @test isapprox(log(pi), logπ) - @test isapprox(log(2pi), log2π) - @test isapprox(log(4pi), log4π) +@testset "rules for $(a)" for a in IRRATIONALS + @testset "Logarithm" begin + if a > 0 + test_with_function(log, a) + else + @test_throws DomainError log(a) + end + end + + @testset "Inverse" begin + test_with_function(inv, a) + test_with_function(t->t^-1, a) + end + + @testset "Exponential" begin + test_with_function(exp, a) + end + + @testset "Triangular" begin + test_with_function(sin, a) + test_with_function(cos, a) + @test sincos(a) == (sin(a),cos(a)) + end + + @testset "Square" begin + test_with_function(t->t*t, a) + test_with_function(abs2, a) + test_with_function(t->t^2, a) + end end +@testset "Multiplicative inverse for (a,b)" for (a,b) in INVERSE_PAIRS + @test a*b === 1.0 + @test b*a === 1.0 +end