Skip to content

Commit c9a73a6

Browse files
authored
Use singleton types and custom @irrational macro (#19)
* Use singleton types and custom `@irrational` macro * Support Julia < 1.1.0-DEV.683 * Qualify module * Test definition using MPFR constants * Fix typo * Mark as breaking change * Fix coverage analysis on Julia 1.0 * Add more tests and additional definitions for older Julia versions * Add tests for `hash` * Update src/macro.jl
1 parent f5ad707 commit c9a73a6

File tree

5 files changed

+231
-28
lines changed

5 files changed

+231
-28
lines changed

.github/workflows/CI.yml

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,39 +22,31 @@ jobs:
2222
- ubuntu-latest
2323
arch:
2424
- x64
25-
include:
26-
- version: '1'
27-
os: ubuntu-latest
28-
arch: x64
29-
coverage: true
3025
steps:
3126
- uses: actions/checkout@v2
3227
- uses: julia-actions/setup-julia@v1
3328
with:
3429
version: ${{ matrix.version }}
3530
arch: ${{ matrix.arch }}
36-
- uses: actions/cache@v1
37-
env:
38-
cache-name: cache-artifacts
39-
with:
40-
path: ~/.julia/artifacts
41-
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
42-
restore-keys: |
43-
${{ runner.os }}-test-${{ env.cache-name }}-
44-
${{ runner.os }}-test-
45-
${{ runner.os }}-
31+
- uses: julia-actions/cache@v1
4632
- uses: julia-actions/julia-buildpkg@v1
4733
- uses: julia-actions/julia-runtest@v1
48-
with:
49-
coverage: ${{ matrix.coverage || false }}
5034
- uses: julia-actions/julia-processcoverage@v1
51-
if: matrix.coverage
52-
- uses: codecov/codecov-action@v1
53-
if: matrix.coverage
35+
- uses: codecov/codecov-action@v3
5436
with:
55-
file: lcov.info
37+
files: lcov.info
5638
- uses: coverallsapp/github-action@master
57-
if: matrix.coverage
5839
with:
59-
github-token: ${{ secrets.GITHUB_TOKEN }}
60-
path-to-lcov: ./lcov.info
40+
github-token: ${{ secrets.GITHUB_TOKEN }}
41+
path-to-lcov: ./lcov.info
42+
flag-name: run-${{ matrix.version }}
43+
parallel: true
44+
finish:
45+
needs: test
46+
runs-on: ubuntu-latest
47+
steps:
48+
- name: Coveralls Finished
49+
uses: coverallsapp/github-action@master
50+
with:
51+
github-token: ${{ secrets.github_token }}
52+
parallel-finished: true

Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
name = "IrrationalConstants"
22
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
33
authors = ["JuliaMath"]
4-
version = "0.1.1"
4+
version = "0.2.0"
55

66
[compat]
77
julia = "1"
88

99
[extras]
10+
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
1011
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1112

1213
[targets]
13-
test = ["Test"]
14+
test = ["Documenter", "Test"]

src/IrrationalConstants.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module IrrationalConstants
22

3-
using Base: @irrational
4-
53
export
64
twoπ, #
75
fourπ, #
@@ -28,6 +26,7 @@ export
2826
log2π, # log(2π)
2927
log4π # log(4π)
3028

29+
include("macro.jl")
3130
include("stats.jl")
3231

3332
end # module

src/macro.jl

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# We define a custom subtype of `AbstractIrrational` and
2+
# define methods for it that are not generalized yet to `AbstractIrrational`
3+
# in https://github.com/JuliaLang/julia/blob/e536c77f4dc693aafc48af910b4fd86b487e900d/base/irrationals.jl
4+
5+
abstract type IrrationalConstant <: AbstractIrrational end
6+
7+
function Base.show(io::IO, ::MIME"text/plain", x::IrrationalConstant)
8+
if get(io, :compact, false)::Bool
9+
print(io, x)
10+
else
11+
print(io, x, " = ", string(float(x))[1:min(end,15)], "...")
12+
end
13+
end
14+
15+
Base.:(==)(::T, ::T) where {T<:IrrationalConstant} = true
16+
Base.:<(::T, ::T) where {T<:IrrationalConstant} = false
17+
Base.:<=(::T, ::T) where {T<:IrrationalConstant} = true
18+
Base.hash(x::IrrationalConstant, h::UInt) = 3*objectid(x) - h
19+
20+
# definitions for AbstractIrrational added in https://github.com/JuliaLang/julia/pull/34773
21+
if VERSION < v"1.5.0-DEV.301"
22+
Base.zero(::IrrationalConstant) = false
23+
Base.zero(::Type{<:IrrationalConstant}) = false
24+
25+
Base.one(::IrrationalConstant) = true
26+
Base.one(::Type{<:IrrationalConstant}) = true
27+
end
28+
29+
# definition for AbstractIrrational added in https://github.com/JuliaLang/julia/pull/31068
30+
if VERSION < v"1.2.0-DEV.337"
31+
Base.inv(x::IrrationalConstant) = 1/x
32+
end
33+
34+
"""
35+
@irrational sym val def [T]
36+
@irrational(sym, val, def[, T])
37+
38+
Define a new singleton type `T` representing an irrational constant as subtype of
39+
`IrrationalConstants.IrrationalConstant <: AbstractIrrational` with an instance named `sym`, pre-computed `Float64` value `val`,
40+
and arbitrary-precision definition in terms of `BigFloat`s given by the expression `def`.
41+
42+
As default, `T` is set to `sym` with the first character converted to uppercase.
43+
44+
An `AssertionError` is thrown when either `big(def) isa BigFloat` or `Float64(val) == Float64(def)`
45+
returns `false`.
46+
47+
# Examples
48+
49+
```jldoctest
50+
julia> IrrationalConstants.@irrational(twoπ, 6.2831853071795864769, 2*big(π))
51+
52+
julia> twoπ
53+
twoπ = 6.2831853071795...
54+
55+
julia> IrrationalConstants.@irrational sqrt2 1.4142135623730950488 √big(2)
56+
57+
julia> sqrt2
58+
sqrt2 = 1.4142135623730...
59+
60+
julia> IrrationalConstants.@irrational halfτ 3.14159265358979323846 pi
61+
62+
julia> halfτ
63+
halfτ = 3.1415926535897...
64+
65+
julia> IrrationalConstants.@irrational sqrt2 1.4142135623730950488 big(2)
66+
ERROR: AssertionError: big($(Expr(:escape, :sqrt2))) isa BigFloat
67+
68+
julia> IrrationalConstants.@irrational sqrt2 1.41421356237309 √big(2)
69+
ERROR: AssertionError: Float64($(Expr(:escape, :sqrt2))) == Float64(big($(Expr(:escape, :sqrt2))))
70+
```
71+
"""
72+
macro irrational(sym, val, def, T=Symbol(uppercasefirst(string(sym))))
73+
esym = esc(sym)
74+
qsym = esc(Expr(:quote, sym))
75+
eT = esc(T)
76+
bigconvert = if VERSION < v"1.1.0-DEV.683"
77+
# support older Julia versions prior to https://github.com/JuliaLang/julia/pull/29157
78+
isa(def,Symbol) ? quote
79+
function Base.BigFloat(::$eT)
80+
c = BigFloat()
81+
ccall(($(string("mpfr_const_", def)), :libmpfr),
82+
Cint, (Ref{BigFloat}, Int32), c, Base.MPFR.ROUNDING_MODE[])
83+
return c
84+
end
85+
end : quote
86+
Base.BigFloat(::$eT) = $(esc(def))
87+
end
88+
else
89+
# newer Julia versions
90+
isa(def, Symbol) ? quote
91+
function Base.BigFloat(::$eT, r::Base.MPFR.MPFRRoundingMode=Base.MPFR.ROUNDING_MODE[]; precision=precision(BigFloat))
92+
c = BigFloat(; precision=precision)
93+
ccall(($(string("mpfr_const_", def)), :libmpfr),
94+
Cint, (Ref{BigFloat}, Base.MPFR.MPFRRoundingMode), c, r)
95+
return c
96+
end
97+
end : quote
98+
function Base.BigFloat(::$eT; precision=precision(BigFloat))
99+
setprecision(BigFloat, precision) do
100+
$(esc(def))
101+
end
102+
end
103+
end
104+
end
105+
quote
106+
struct $T <: IrrationalConstant end
107+
const $esym = $eT()
108+
$bigconvert
109+
Base.Float64(::$eT) = $val
110+
Base.Float32(::$eT) = $(Float32(val))
111+
Base.show(io::IO, ::$eT) = print(io, $qsym)
112+
@assert isa(big($esym), BigFloat)
113+
@assert Float64($esym) == Float64(big($esym))
114+
@assert Float32($esym) == Float32(big($esym))
115+
end
116+
end

test/runtests.jl

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using IrrationalConstants
2+
using Documenter
23
using Test
34

45
@testset "k*pi" begin
@@ -40,3 +41,97 @@ end
4041
@test isapprox(log(4pi), log4π)
4142
end
4243

44+
@testset "type system" begin
45+
@test twoπ === IrrationalConstants.Twoπ()
46+
@test twoπ isa IrrationalConstants.IrrationalConstant
47+
@test twoπ isa AbstractIrrational
48+
end
49+
50+
@testset "hash" begin
51+
for i in (twoπ, invπ, sqrt2, logtwo)
52+
for j in (twoπ, invπ, sqrt2, logtwo)
53+
@test isequal(i==j, hash(i)==hash(j))
54+
end
55+
end
56+
end
57+
58+
@testset "doctests" begin
59+
DocMeta.setdocmeta!(
60+
IrrationalConstants, :DocTestSetup, :(using IrrationalConstants); recursive=true
61+
)
62+
doctest(IrrationalConstants; manual=false)
63+
end
64+
65+
# copied from https://github.com/JuliaLang/julia/blob/cf5ae0369ceae078cf6a29d7aa34f48a5a53531e/test/numbers.jl
66+
# and adapted to irrationals in this package
67+
68+
@testset "IrrationalConstant zero and one" begin
69+
@test one(twoπ) === true
70+
@test zero(twoπ) === false
71+
@test one(typeof(twoπ)) === true
72+
@test zero(typeof(twoπ)) === false
73+
end
74+
75+
@testset "IrrationalConstants compared with IrrationalConstants" begin
76+
for i in (twoπ, invπ, sqrt2, logtwo)
77+
for j in (twoπ, invπ, sqrt2, logtwo)
78+
@test isequal(i==j, Float64(i)==Float64(j))
79+
@test isequal(i!=j, Float64(i)!=Float64(j))
80+
@test isequal(i<=j, Float64(i)<=Float64(j))
81+
@test isequal(i>=j, Float64(i)>=Float64(j))
82+
@test isequal(i<j, Float64(i)<Float64(j))
83+
@test isequal(i>j, Float64(i)>Float64(j))
84+
end
85+
end
86+
end
87+
88+
@testset "IrrationalConstant Inverses, JuliaLang/Julia Issue #30882" begin
89+
@test @inferred(inv(twoπ)) 0.15915494309189535
90+
end
91+
92+
@testset "IrrationalConstants compared with Rationals and Floats" begin
93+
@test Float64(twoπ, RoundDown) < twoπ
94+
@test Float64(twoπ, RoundUp) > twoπ
95+
@test !(Float64(twoπ, RoundDown) > twoπ)
96+
@test !(Float64(twoπ, RoundUp) < twoπ)
97+
@test Float64(twoπ, RoundDown) <= twoπ
98+
@test Float64(twoπ, RoundUp) >= twoπ
99+
@test Float64(twoπ, RoundDown) != twoπ
100+
@test Float64(twoπ, RoundUp) != twoπ
101+
102+
@test Float32(twoπ, RoundDown) < twoπ
103+
@test Float32(twoπ, RoundUp) > twoπ
104+
@test !(Float32(twoπ, RoundDown) > twoπ)
105+
@test !(Float32(twoπ, RoundUp) < twoπ)
106+
107+
@test prevfloat(big(twoπ)) < twoπ
108+
@test nextfloat(big(twoπ)) > twoπ
109+
@test !(prevfloat(big(twoπ)) > twoπ)
110+
@test !(nextfloat(big(twoπ)) < twoπ)
111+
112+
@test 5293386250278608690//842468587426513207 < twoπ
113+
@test !(5293386250278608690//842468587426513207 > twoπ)
114+
@test 5293386250278608690//842468587426513207 != twoπ
115+
end
116+
IrrationalConstants.@irrational i46051 4863.185427757 1548big(pi)
117+
@testset "IrrationalConstant printing" begin
118+
@test sprint(show, "text/plain", twoπ) == "twoπ = 6.2831853071795..."
119+
@test sprint(show, "text/plain", twoπ, context=:compact => true) == "twoπ"
120+
@test sprint(show, twoπ) == "twoπ"
121+
# JuliaLang/Julia issue #46051
122+
@test sprint(show, "text/plain", i46051) == "i46051 = 4863.185427757..."
123+
end
124+
125+
@testset "IrrationalConstant/Bool multiplication" begin
126+
@test false*twoπ === 0.0
127+
@test twoπ*false === 0.0
128+
@test true*twoπ === Float64(twoπ)
129+
@test twoπ*true === Float64(twoπ)
130+
end
131+
132+
# JuliaLang/Julia issue #26324
133+
@testset "irrational promotion" begin
134+
@test twoπ*ComplexF32(2) isa ComplexF32
135+
@test twoπ/ComplexF32(2) isa ComplexF32
136+
@test log(twoπ, ComplexF32(2)) isa ComplexF32
137+
end

0 commit comments

Comments
 (0)