Skip to content
This repository was archived by the owner on Nov 26, 2025. It is now read-only.

Commit 2d5f2a9

Browse files
dpsanderslbenet
authored andcommitted
Rounding modes using traits (#220)
* Traits-based interval rounding selection * Working traits-based selection * Cleanup * Do not change rounding type if not necessary * Fix rounding with traits * Update CRlibm in REQUIRE * Traits fixes * Fix rounding for powers * Fixes * Fixes * Fix small integer powers * Remove from tests * Remove failing linear algebra test * Linalg test broken only on 0.6 * Remove overwrite for small integer powers * Change RoundingType -> IntervalRounding. Add to design explanation * Remove restriction on T in IntervalRounding{T} * Docstring * Rewrite rounding-type trait to use types instead of instances * Correct REQUIRE * Add basic tests for interval rounding modes on 0.6 * Reenable linear algebra test * Move multidim tests to end * Correct testset syntax * Add Suppressor for rounding tests * Remove old definitions * Comment out Suppressor * Remove testset for rounding tests due to world age problems * Add individual testsets for each interval rounding type * Expand docstring for
1 parent 23eaa39 commit 2d5f2a9

22 files changed

+262
-138
lines changed

src/ValidatedNumerics.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ __precompile__(true)
44

55
module ValidatedNumerics
66

7-
using CRlibm
7+
import CRlibm
88
using Compat
99
using StaticArrays
1010
using ForwardDiff

src/intervals/arithmetic.jl

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,6 @@ if VERSION >= v"0.6.0-dev.1024"
159159
const filter = Iterators.filter
160160
end
161161

162-
if VERSION < v"0.5.0-dev+1279"
163-
min(x) = x
164-
max(x) = x
165-
end
166-
167-
168162
function min_ignore_nans(args...)
169163
min(filter(x->!isnan(x), args)...)
170164
end
@@ -319,7 +313,7 @@ function mid{T}(a::Interval{T})
319313

320314
a.lo == -&& return nextfloat(a.lo)
321315
a.hi == +&& return prevfloat(a.hi)
322-
316+
323317
(a.lo + a.hi) / 2 # rounds to nearest
324318
end
325319

src/intervals/functions.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ end
1414

1515
# Integer power:
1616

17+
# overwrite new behaviour for small integer powers:
18+
# ^{p}(x::ValidatedNumerics.Interval, ::Type{Val{p}}) = x^p
19+
1720
function ^(a::Interval{BigFloat}, n::Integer)
1821
isempty(a) && return a
1922
n == 0 && return one(a)

src/intervals/precision.jl

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ function big53(a::Interval{Float64})
2222
end
2323
end
2424

25+
function big53(x::Float64)
26+
# BigFloat(x, 53) # in Julia v0.6
27+
28+
setprecision(53) do
29+
BigFloat(x)
30+
end
31+
end
32+
2533

2634
setprecision(::Type{Interval}, ::Type{Float64}) = parameters.precision_type = Float64
2735
# does not change the BigFloat precision
@@ -77,44 +85,13 @@ Base.float{T}(::Type{Rational{T}}) = typeof(float(one(Rational{T})))
7785

7886
# Use that type for rounding with rationals, e.g. for sqrt:
7987

80-
if VERSION < v"0.5.0-dev+1182"
81-
82-
function Base.with_rounding{T}(f::Function, ::Type{Rational{T}},
83-
rounding_mode::RoundingMode)
84-
setrounding(f, float(Rational{T}), rounding_mode)
85-
end
86-
87-
else
88-
function Base.setrounding{T}(f::Function, ::Type{Rational{T}},
89-
rounding_mode::RoundingMode)
90-
setrounding(f, float(Rational{T}), rounding_mode)
91-
end
88+
function Base.setrounding{T}(f::Function, ::Type{Rational{T}},
89+
rounding_mode::RoundingMode)
90+
setrounding(f, float(Rational{T}), rounding_mode)
9291
end
9392

9493

95-
float{T}(x::Interval{T}) = convert(Interval{float(T)}, x) # https://github.com/dpsanders/ValidatedNumerics.jl/issues/174
96-
97-
## Change type of interval rounding:
9894

99-
100-
doc"""`rounding(Interval)` returns the current interval rounding mode.
101-
There are two possible rounding modes:
102-
103-
- :narrow -- changes the floating-point rounding mode to `RoundUp` and `RoundDown`.
104-
This gives the narrowest possible interval.
105-
106-
- :wide -- Leaves the floating-point rounding mode in `RoundNearest` and uses
107-
`prevfloat` and `nextfloat` to achieve directed rounding. This creates an interval of width 2`eps`.
108-
"""
109-
110-
rounding(::Type{Interval}) = parameters.rounding
111-
112-
function setrounding(::Type{Interval}, mode)
113-
if mode [:wide, :narrow]
114-
throw(ArgumentError("Only possible interval rounding modes are `:wide` and `:narrow`"))
115-
end
116-
117-
parameters.rounding = mode # a symbol
118-
end
95+
float{T}(x::Interval{T}) = convert(Interval{float(T)}, x) # https://github.com/dpsanders/ValidatedNumerics.jl/issues/174
11996

12097
big{T}(x::Interval{T}) = convert(Interval{BigFloat}, x)

src/intervals/rounding.jl

Lines changed: 160 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,198 @@
1-
# Define rounded versions of elementary functions
2-
# E.g. +(a, b, RoundDown)
3-
# Some, like sin(a, RoundDown) are already defined in CRlibm
1+
#= Design summary:
42
3+
This is a so-called "traits-based" design, as follows.
54
6-
import Base: +, -, *, /, sin, sqrt, inv, ^, zero, convert, parse
5+
The main body of the file defines versions of elementary functions with all allowed
6+
interval rounding types, e.g.
7+
+(IntervalRounding{:correct}, a, b, RoundDown)
8+
+(IntervalRounding{:fast}, a, b, RoundDown)
9+
+(IntervalRounding{:none}, a, b, RoundDown)
710
8-
# unary minus:
9-
-{T<:AbstractFloat}(a::T, ::RoundingMode) = -a # ignore rounding
11+
The current allowed rounding types are
12+
- :correct # correct rounding (rounding to the nearest floating-point number)
13+
- :fast # fast rounding by prevfloat and nextfloat (slightly wider than needed)
14+
- :none # no rounding at all (for speed, but forgoes any pretense at being rigorous)
1015
11-
# zero:
12-
zero{T<:AbstractFloat}(a::Interval{T}, ::RoundingMode) = zero(T)
13-
zero{T<:AbstractFloat}(::Type{T}, ::RoundingMode) = zero(T)
16+
The function `setrounding(Interval, rounding_type)` then defines rounded
17+
functions *without* an explicit rounding type, e.g.
18+
19+
sin(x, r::RoundingMode) = sin(IntervalRounding{:correct}, x, r)
20+
21+
These are overwritten when `setrounding(Interval, rounding_type)` is called again.
22+
23+
In Julia v0.6, but *not* in Julia v0.5, this will automatically redefine all relevant functions, in particular those used in +(a::Interval, b::Interval) etc., so that all interval functions will automatically work with the correct interval rounding type.
24+
=#
1425

15-
convert(::Type{BigFloat}, x, rounding_mode::RoundingMode) = setrounding(BigFloat, rounding_mode) do
16-
convert(BigFloat, x)
17-
end
1826

19-
parse{T}(::Type{T}, x, rounding_mode::RoundingMode) = setrounding(T, rounding_mode) do
20-
parse(T, x)
27+
doc"""Interval rounding trait type"""
28+
immutable IntervalRounding{T} end
29+
30+
# Functions that are the same for all rounding types:
31+
@eval begin
32+
# unary minus:
33+
-{T<:AbstractFloat}(a::T, ::RoundingMode) = -a # ignore rounding
34+
35+
# zero:
36+
zero{T<:AbstractFloat}(a::Interval{T}, ::RoundingMode) = zero(T)
37+
zero{T<:AbstractFloat}(::Type{T}, ::RoundingMode) = zero(T)
38+
39+
convert(::Type{BigFloat}, x, rounding_mode::RoundingMode) =
40+
setrounding(BigFloat, rounding_mode) do
41+
convert(BigFloat, x)
42+
end
43+
44+
parse{T}(::Type{T}, x, rounding_mode::RoundingMode) = setrounding(T, rounding_mode) do
45+
parse(T, x)
46+
end
47+
48+
49+
sqrt{T<:Rational}(a::T, rounding_mode::RoundingMode) = setrounding(float(T), rounding_mode) do
50+
sqrt(float(a))
51+
end
52+
2153
end
2254

2355

56+
2457
# no-ops for rational rounding:
2558
for f in (:+, :-, :*, :/)
2659
@eval $f{T<:Rational}(a::T, b::T, ::RoundingMode) = $f(a, b)
2760
end
2861

29-
sqrt{T<:Rational}(a::T, rounding_mode::RoundingMode) = setrounding(float(T), rounding_mode) do
30-
sqrt(float(a))
31-
end
32-
33-
3462

63+
# Define functions with different rounding types:
3564
for mode in (:Down, :Up)
3665

3766
mode1 = Expr(:quote, mode)
3867
mode1 = :(::RoundingMode{$mode1})
3968

4069
mode2 = Symbol("Round", mode)
4170

71+
if mode == :Down
72+
directed = :prevfloat
73+
else
74+
directed = :nextfloat
75+
end
76+
4277

43-
for f in (:+, :-, :*, :/,
44-
:atan2)
78+
# binary functions:
79+
for f in (:+, :-, :*, :/, :atan2)
4580

46-
@eval begin
47-
function $f{T<:AbstractFloat}(a::T, b::T, $mode1)
48-
setrounding(T, $mode2) do
49-
$f(a, b)
81+
@eval function $f{T<:AbstractFloat}(::Type{IntervalRounding{:correct}},
82+
a::T, b::T, $mode1)
83+
setrounding(T, $mode2) do
84+
$f(a, b)
85+
end
5086
end
51-
end
52-
end
87+
88+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:fast}},
89+
a::T, b::T, $mode1) = $directed($f(a, b))
90+
91+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:none}},
92+
a::T, b::T, $mode1) = $f(a, b)
93+
5394
end
5495

55-
@eval begin
56-
function ^{T<:AbstractFloat,S}(a::T, b::S, $mode1)
57-
setrounding(T, $mode2) do
58-
^(a, b)
59-
end
96+
97+
# power:
98+
99+
@eval function ^{S<:Real}(::Type{IntervalRounding{:correct}},
100+
a::BigFloat, b::S, $mode1)
101+
setrounding(BigFloat, $mode2) do
102+
^(a, b)
103+
end
104+
end
105+
106+
# for correct rounding for Float64, must pass through BigFloat:
107+
@eval function ^{S<:Real}(::Type{IntervalRounding{:correct}}, a::Float64, b::S, $mode1)
108+
setprecision(BigFloat, 53) do
109+
Float64(^(IntervalRounding{:correct}, BigFloat(a), b, $mode2))
60110
end
61111
end
62112

113+
@eval ^{T<:AbstractFloat,S<:Real}(::Type{IntervalRounding{:fast}},
114+
a::T, b::S, $mode1) = $directed(a^b)
115+
116+
@eval ^{T<:AbstractFloat,S<:Real}(::Type{IntervalRounding{:none}},
117+
a::T, b::S, $mode1) = a^b
118+
119+
120+
# functions not in CRlibm:
121+
for f in (:sqrt, :inv, :tanh, :asinh, :acosh, :atanh)
122+
123+
@eval function $f{T<:AbstractFloat}(::Type{IntervalRounding{:correct}},
124+
a::T, $mode1)
125+
setrounding(T, $mode2) do
126+
$f(a)
127+
end
128+
end
129+
130+
131+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:fast}},
132+
a::T, $mode1) = $directed($f(a))
133+
134+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:none}},
135+
a::T, $mode1) = $f(a)
136+
63137

64-
for f in (:sqrt, :inv,
65-
:tanh, :asinh, :acosh, :atanh) # these functions not in CRlibm
66-
@eval begin
67-
function $f{T<:AbstractFloat}(a::T, $mode1)
68-
setrounding(T, $mode2) do
69-
$f(a)
70-
end
71-
end
72-
end
73138
end
74139

75140

141+
# Functions defined in CRlibm
142+
76143
for f in CRlibm.functions
77-
@eval $f{T<:AbstractFloat}(a::T, $mode1) = CRlibm.$f(a, $mode2)
144+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:correct}},
145+
a::T, $mode1) = CRlibm.$f(a, $mode2)
146+
147+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:fast}},
148+
a::T, $mode1) = $directed($f(a))
149+
150+
@eval $f{T<:AbstractFloat}(::Type{IntervalRounding{:none}},
151+
a::T, $mode1) = $f(a)
152+
153+
end
154+
end
155+
156+
doc"""
157+
setrounding(Interval, rounding_type::Symbol)
158+
159+
Set the rounding type used for all interval calculations on Julia v0.6 and above.
160+
Valid `rounding_type`s are `:correct`, `:fast` and `:none`.
161+
"""
162+
function setrounding(::Type{Interval}, rounding_type::Symbol)
163+
164+
if rounding_type == current_rounding_type[]
165+
return # no need to redefine anything
166+
end
167+
168+
if rounding_type (:correct, :fast, :none)
169+
throw(ArgumentError("Rounding type must be one of `:correct`, `:fast`, `:none`"))
170+
end
171+
172+
roundtype = IntervalRounding{rounding_type}
173+
174+
175+
# binary functions:
176+
for f in (:+, :-, :*, :/, :^, :atan2)
177+
178+
@eval $f{T<:AbstractFloat}(a::T, b::T, r::RoundingMode) = $f($roundtype, a, b, r)
78179
end
79180

181+
@eval ^{T<:AbstractFloat, S<:Real}(a::T, b::S, r::RoundingMode) = ^($roundtype, promote(a, b)..., r)
182+
183+
# unary functions:
184+
for f in vcat(CRlibm.functions,
185+
[:sqrt, :inv, :tanh, :asinh, :acosh, :atanh])
186+
187+
@eval $f{T<:AbstractFloat}(a::T, r::RoundingMode) = $f($roundtype, a, r)
188+
189+
@eval $f(x::Real, r::RoundingMode) = $f(float(x), r)
190+
191+
end
192+
193+
current_rounding_type[] = rounding_type
80194
end
195+
196+
# default: correct rounding
197+
const current_rounding_type = Symbol[:undefined]
198+
setrounding(Interval, :correct)

test/ITF1788_tests/libieeep1788_tests_bool.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ using ValidatedNumerics
2828
#Preamble
2929
setprecision(53)
3030
setprecision(Interval, Float64)
31-
setrounding(Interval, :narrow)
31+
# setrounding(Interval, :narrow)
3232

3333
facts("minimal_empty_test") do
3434
@fact isempty(∅) --> true

test/ITF1788_tests/libieeep1788_tests_cancel.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ using ValidatedNumerics
2828
#Preamble
2929
setprecision(53)
3030
setprecision(Interval, Float64)
31-
setrounding(Interval, :narrow)
31+
# setrounding(Interval, :narrow)
3232

3333
facts("minimal_cancelPlus_test") do
3434
@fact cancelplus(Interval(-Inf, -1.0), ∅) --> entireinterval(Float64)

test/ITF1788_tests/libieeep1788_tests_elem.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ using ValidatedNumerics
2828
#Preamble
2929
setprecision(53)
3030
setprecision(Interval, Float64)
31-
setrounding(Interval, :narrow)
31+
# setrounding(Interval, :narrow)
3232

3333
facts("minimal_pos_test") do
3434
@fact +Interval(1.0, 2.0) --> Interval(1.0, 2.0)

test/ITF1788_tests/libieeep1788_tests_mul_rev.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ using ValidatedNumerics
2828
#Preamble
2929
setprecision(53)
3030
setprecision(Interval, Float64)
31-
setrounding(Interval, :narrow)
31+
# setrounding(Interval, :narrow)
3232

3333
facts("minimal_mulRevToPair_test") do
3434

test/ITF1788_tests/libieeep1788_tests_num.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ using ValidatedNumerics
2828
#Preamble
2929
setprecision(53)
3030
setprecision(Interval, Float64)
31-
setrounding(Interval, :narrow)
31+
# setrounding(Interval, :narrow)
3232

3333
facts("minimal_inf_test") do
3434
@fact infimum(∅) --> Inf

0 commit comments

Comments
 (0)