Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exact rules for irrational numbers #14

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
1 change: 1 addition & 0 deletions src/IrrationalConstants.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ export
log4π # log(4π)

include("stats.jl")
include("rules.jl")

end # module
68 changes: 68 additions & 0 deletions src/rules.jl
Original file line number Diff line number Diff line change
@@ -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π)
153 changes: 122 additions & 31 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function test_with_function(f, a::Irrational)
function test_with_function(f, a::AbstractIrrational)
MethodError: no method matching (::var"#test_with_function#4")(::typeof(sin), ::IrrationalConstants.Log4π)

  Closest candidates are:
    (::var"#test_with_function#4")(::Any, ::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