Skip to content

Commit

Permalink
Merge pull request #15227 from pkofod/edgerationals
Browse files Browse the repository at this point in the history
Fix edge cases in rounding of Rational(num, den)
  • Loading branch information
tkelman committed Feb 29, 2016
2 parents 2ac9bc9 + a88dbae commit 993bdab
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 22 deletions.
63 changes: 50 additions & 13 deletions base/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -288,23 +288,60 @@ trunc{T}(::Type{T}, x::Rational) = convert(T,div(x.num,x.den))
floor{T}(::Type{T}, x::Rational) = convert(T,fld(x.num,x.den))
ceil{ T}(::Type{T}, x::Rational) = convert(T,cld(x.num,x.den))

function round{T}(::Type{T}, x::Rational, ::RoundingMode{:Nearest})
q,r = divrem(x.num,x.den)
s = abs(r) < (x.den+one(x.den)+iseven(q))>>1 ? q : q+copysign(one(q),x.num)
convert(T,s)

function round{T, Tr}(::Type{T}, x::Rational{Tr}, ::RoundingMode{:Nearest})
if den(x) == zero(Tr) && T <: Integer
throw(DivideError())
elseif den(x) == zero(Tr)
return convert(T, copysign(one(Tr)//zero(Tr), num(x)))
end
q,r = divrem(num(x), den(x))
s = q
if abs(r) >= abs((den(x)-copysign(Tr(4), num(x))+one(Tr)+iseven(q))>>1 + copysign(Tr(2), num(x)))
s += copysign(one(Tr),num(x))
end
convert(T, s)
end

round{T}(::Type{T}, x::Rational) = round(T, x, RoundNearest)

function round{T, Tr}(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesAway})
if den(x) == zero(Tr) && T <: Integer
throw(DivideError())
elseif den(x) == zero(Tr)
return convert(T, copysign(one(Tr)//zero(Tr), num(x)))
end
q,r = divrem(num(x), den(x))
s = q
if abs(r) >= abs((den(x)-copysign(Tr(4), num(x))+one(Tr))>>1 + copysign(Tr(2), num(x)))
s += copysign(one(Tr),num(x))
end
convert(T, s)
end
round{T}(::Type{T}, x::Rational) = round(T,x,RoundNearest)
function round{T}(::Type{T}, x::Rational, ::RoundingMode{:NearestTiesAway})
q,r = divrem(x.num,x.den)
s = abs(r) < (x.den+one(x.den))>>1 ? q : q+copysign(one(q),x.num)
convert(T,s)

function round{T, Tr}(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesUp})
if den(x) == zero(Tr) && T <: Integer
throw(DivideError())
elseif den(x) == zero(Tr)
return convert(T, copysign(one(Tr)//zero(Tr), num(x)))
end
q,r = divrem(num(x), den(x))
s = q
if abs(r) >= abs((den(x)-copysign(Tr(4), num(x))+one(Tr)+(num(x)<0))>>1 + copysign(Tr(2), num(x)))
s += copysign(one(Tr),num(x))
end
convert(T, s)
end
function round{T}(::Type{T}, x::Rational, ::RoundingMode{:NearestTiesUp})
q,r = divrem(x.num,x.den)
s = abs(r) < (x.den+one(x.den)+(x.num<0))>>1 ? q : q+copysign(one(q),x.num)
convert(T,s)

function round{T}(::Type{T}, x::Rational{Bool})
if den(x) == false && issubtype(T, Union{Integer, Bool})
throw(DivideError())
end
convert(T, x)
end

round{T}(::Type{T}, x::Rational{Bool}, ::RoundingMode) = round(T, x)

trunc{T}(x::Rational{T}) = Rational(trunc(T,x))
floor{T}(x::Rational{T}) = Rational(floor(T,x))
ceil{ T}(x::Rational{T}) = Rational(ceil(T,x))
Expand Down
48 changes: 39 additions & 9 deletions test/numbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2782,15 +2782,12 @@ for Tf = (Float16, Float32, Float64), Ti = (Int16, Int32, Int64)
over_half = Rational(div(typemax(Ti),Ti(2))+one(Ti), typemax(Ti))
exactly_half = Rational(one(Ti) , Ti(2))

@test round(Tf, Rational(1,2), RoundNearestTiesUp) == 1.0
@test round(Tf, Rational(1,2), RoundNearestTiesAway) == 1.0

# @test round( almost_half) == 0//1
# @test round(-almost_half) == 0//1
# @test round(Tf, almost_half, RoundNearestTiesUp) == 0.0
# @test round(Tf, -almost_half, RoundNearestTiesUp) == 0.0
# @test round(Tf, almost_half, RoundNearestTiesAway) == 0.0
# @test round(Tf, -almost_half, RoundNearestTiesAway) == 0.0
@test round( almost_half) == 0//1
@test round(-almost_half) == 0//1
@test round(Tf, almost_half, RoundNearestTiesUp) == 0.0
@test round(Tf, -almost_half, RoundNearestTiesUp) == 0.0
@test round(Tf, almost_half, RoundNearestTiesAway) == 0.0
@test round(Tf, -almost_half, RoundNearestTiesAway) == 0.0

@test round( exactly_half) == 0//1 # rounds to closest _even_ integer
@test round(-exactly_half) == 0//1 # rounds to closest _even_ integer
Expand All @@ -2806,6 +2803,22 @@ for Tf = (Float16, Float32, Float64), Ti = (Int16, Int32, Int64)
@test round(Tf, over_half, RoundNearestTiesAway) == 1.0
@test round(Tf, -over_half, RoundNearestTiesUp) == -1.0
@test round(Tf, -over_half, RoundNearestTiesAway) == -1.0

@test round(Tf, 11//2, RoundNearestTiesUp) == 6.0
@test round(Tf, -11//2, RoundNearestTiesUp) == -5.0
@test round(Tf, 11//2, RoundNearestTiesAway) == 6.0
@test round(Tf, -11//2, RoundNearestTiesAway) == -6.0

@test round(Tf, Ti(-1)//zero(Ti)) == -Inf
@test round(Tf, one(1)//zero(Ti)) == Inf
@test round(Tf, Ti(-1)//zero(Ti), RoundNearestTiesUp) == -Inf
@test round(Tf, one(1)//zero(Ti), RoundNearestTiesUp) == Inf
@test round(Tf, Ti(-1)//zero(Ti), RoundNearestTiesAway) == -Inf
@test round(Tf, one(1)//zero(Ti), RoundNearestTiesAway) == Inf

@test round(Tf, zero(Ti)//one(Ti)) == 0
@test round(Tf, zero(Ti)//one(Ti), RoundNearestTiesUp) == 0
@test round(Tf, zero(Ti)//one(Ti), RoundNearestTiesAway) == 0
end

let
Expand All @@ -2826,3 +2839,20 @@ let
@test read(io2, typeof(rational2)) == rational2
end
end

@test round(11//2) == 6//1 # rounds to closest _even_ integer
@test round(-11//2) == -6//1 # rounds to closest _even_ integer
@test round(11//3) == 4//1 # rounds to closest _even_ integer
@test round(-11//3) == -4//1 # rounds to closest _even_ integer

for T in (Float16, Float32, Float64)
@test round(T, true//false) === convert(T, Inf)
@test round(T, true//true) === one(T)
@test round(T, false//true) === zero(T)
end

for T in (Int8, Int16, Int32, Int64, Bool)
@test_throws DivideError round(T, true//false)
@test round(T, true//true) === one(T)
@test round(T, false//true) === zero(T)
end

0 comments on commit 993bdab

Please sign in to comment.