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
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
64 changes: 64 additions & 0 deletions src/rules.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## Square
SQUARE_PAIRS = (
Copy link
Member

Choose a reason for hiding this comment

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

I'd put these variables in a let block (or do for (a,b) in (...)), otherwise you're bloating the compiled cache with stuff which isn't needed at runtime

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review! I'll update the code later.

(sqrt2, 2.0),
(sqrt3, 3.0),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure these should be (sqrt2, 2.0), (sqrt3, 3.0) or (sqrt2, 2), (sqrt3, 3). I was referring to sin(π).

julia> versioninfo()
Julia Version 1.9.0-DEV.428
Commit f6a95348fe (2022-04-21 11:53 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: 16 × AMD Ryzen 7 2700X Eight-Core Processor
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-13.0.1 (ORCJIT, znver1)
  Threads: 1 on 16 virtual cores

julia> sin(π)
0.0

julia> cos(π)
-1.0

Copy link
Member

Choose a reason for hiding this comment

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

I think they should be Float64, as explained in my other comment.

(sqrtπ, π),
(sqrt2π, twoπ),
(sqrt4π, fourπ),
(sqrthalfπ, halfπ),
(invsqrt2, 0.5),
(invsqrtπ, invπ),
(invsqrt2π, inv2π),
)
for (a,b) in SQUARE_PAIRS
Base.:(*)(::typeof(a), ::typeof(a)) = b
Base.literal_pow(::typeof(^), ::typeof(a), ::Val{2}) = b
end

## Inverse
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)) = b
Base.literal_pow(::typeof(^), ::typeof(a), ::Val{-1}) = b
end
Base.inv(::typeof(b)) = a
Base.literal_pow(::typeof(^), ::typeof(b), ::Val{-1}) = a
Base.:(*)(::typeof(a), ::typeof(b)) = one(Irrational)
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π}) = invsqrt2
Base.cos(::Irrational{:quartπ}) = invsqrt2
Base.sincos(::Irrational{:quartπ}) = (invsqrt2, 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π}) = π
Base.exp(::Irrational{:log2π}) = twoπ
Base.exp(::Irrational{:log4π}) = fourπ

## Logarithm
# Base.log(::Irrational{:π}) = logπ # Avoid type piracy
Base.log(::Irrational{:twoπ}) = log2π
Base.log(::Irrational{:fourπ}) = log4π
136 changes: 104 additions & 32 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,42 +1,114 @@
using IrrationalConstants
using Test

@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
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

@testset "1/(k*pi)" begin
@test isapprox(1/(2pi), inv2π)
@test isapprox(1/(4pi), inv4π)
end
# If f(a) is approximately equal to a value in IRRATIONALS, f(a) should be Irrational.
@test (b .≈ IRRATIONALS) == (b .=== IRRATIONALS)

@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π)
# 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 "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 "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 "rules for $(a)" for a in IRRATIONALS
@testset "log" begin
if a > 0
test_with_function(log, a)
else
@test_throws DomainError log(a)
end
end

@testset "inv" begin
test_with_function(inv, a)
test_with_function(t->t^-1, a)
end

@testset "exp" begin
test_with_function(exp, a)
end

@testset "sin" 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