Skip to content

Commit

Permalink
Round from zero support for non-BigFloats (#41246)
Browse files Browse the repository at this point in the history
Co-authored-by: Steven G. Johnson <stevenj@alum.mit.edu>

Co-authored-by: Steven G. Johnson <stevenj@mit.edu>

Co-authored-by: Mosè Giordano <giordano@users.noreply.github.com>

Co-authored-by: Dilum Aluthge <dilum@aluthge.com>
  • Loading branch information
jessymilare and DilumAluthge committed Apr 12, 2022
1 parent 8020549 commit c2da085
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 18 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Expand Up @@ -44,6 +44,7 @@ Library changes
now be called on a dictionary or set shared by arbitrary tasks provided that there are no
tasks mutating the dictionary or set ([#44534]).
* Predicate function negation `!f` now returns a composed function `(!) ∘ f` instead of an anonymous function ([#44752]).
* `RoundFromZero` now works for non-`BigFloat` types ([#41246]).


Standard library changes
Expand Down
28 changes: 27 additions & 1 deletion base/div.jl
Expand Up @@ -17,6 +17,9 @@ without any intermediate rounding.
See also [`fld`](@ref) and [`cld`](@ref), which are special cases of this function.
!!! compat "Julia 1.9"
`RoundFromZero` requires at least Julia 1.9.
# Examples:
```jldoctest
julia> div(4, 3, RoundDown) # Matches fld(4, 3)
Expand All @@ -33,6 +36,10 @@ julia> div(-5, 2, RoundNearestTiesAway)
-3
julia> div(-5, 2, RoundNearestTiesUp)
-2
julia> div(4, 3, RoundFromZero)
2
julia> div(-4, 3, RoundFromZero)
-2
```
"""
div(x, y, r::RoundingMode)
Expand Down Expand Up @@ -63,6 +70,13 @@ without any intermediate rounding.
`[0,-y)` otherwise. The result may not be exact if `x` and `y` have the same sign, and
`abs(x) < abs(y)`. See also [`RoundUp`](@ref).
- if `r == RoundFromZero`, then the result is in the interval `(-y, 0]` if `y` is positive, or
`[0, -y)` otherwise. The result may not be exact if `x` and `y` have the same sign, and
`abs(x) < abs(y)`. See also [`RoundFromZero`](@ref).
!!! compat "Julia 1.9"
`RoundFromZero` requires at least Julia 1.9.
# Examples:
```jldoctest
julia> x = 9; y = 4;
Expand All @@ -86,6 +100,10 @@ rem(x, y, ::RoundingMode{:Up}) = mod(x, -y)
rem(x, y, r::RoundingMode{:Nearest}) = x - y*div(x, y, r)
rem(x::Integer, y::Integer, r::RoundingMode{:Nearest}) = divrem(x, y, r)[2]

function rem(x, y, ::typeof(RoundFromZero))
signbit(x) == signbit(y) ? rem(x, y, RoundUp) : rem(x, y, RoundDown)
end

"""
fld(x, y)
Expand Down Expand Up @@ -240,6 +258,10 @@ function divrem(x::Integer, y::Integer, rnd::typeof(RoundNearestTiesUp))
end
end

function divrem(x, y, ::typeof(RoundFromZero))
signbit(x) == signbit(y) ? divrem(x, y, RoundUp) : divrem(x, y, RoundDown)
end

"""
fldmod(x, y)
Expand Down Expand Up @@ -276,12 +298,16 @@ function div(x::Integer, y::Integer, rnd::Union{typeof(RoundNearest),
divrem(x, y, rnd)[1]
end

function div(x::Integer, y::Integer, ::typeof(RoundFromZero))
signbit(x) == signbit(y) ? div(x, y, RoundUp) : div(x, y, RoundDown)
end

# For bootstrapping purposes, we define div for integers directly. Provide the
# generic signature also
div(a::T, b::T, ::typeof(RoundToZero)) where {T<:Union{BitSigned, BitUnsigned64}} = div(a, b)
div(a::Bool, b::Bool, r::RoundingMode) = div(a, b)
# Prevent ambiguities
for rm in (RoundUp, RoundDown, RoundToZero)
for rm in (RoundUp, RoundDown, RoundToZero, RoundFromZero)
@eval div(a::Bool, b::Bool, r::$(typeof(rm))) = div(a, b)
end
function div(x::Bool, y::Bool, rnd::Union{typeof(RoundNearest),
Expand Down
4 changes: 4 additions & 0 deletions base/floatfuncs.jl
Expand Up @@ -236,6 +236,10 @@ function round(x::T, ::RoundingMode{:NearestTiesUp}) where {T <: AbstractFloat}
copysign(floor((x + (T(0.25) - eps(T(0.5)))) + (T(0.25) + eps(T(0.5)))), x)
end

function Base.round(x::AbstractFloat, ::typeof(RoundFromZero))
signbit(x) ? round(x, RoundDown) : round(x, RoundUp)
end

# isapprox: approximate equality of numbers
"""
isapprox(x, y; atol::Real=0, rtol::Real=atol>0 ? 0 : √eps, nans::Bool=false[, norm::Function])
Expand Down
13 changes: 10 additions & 3 deletions base/rounding.jl
Expand Up @@ -37,9 +37,13 @@ Currently supported rounding modes are:
- [`RoundNearestTiesAway`](@ref)
- [`RoundNearestTiesUp`](@ref)
- [`RoundToZero`](@ref)
- [`RoundFromZero`](@ref) ([`BigFloat`](@ref) only)
- [`RoundFromZero`](@ref)
- [`RoundUp`](@ref)
- [`RoundDown`](@ref)
!!! compat "Julia 1.9"
`RoundFromZero` requires at least Julia 1.9. Prior versions support
`RoundFromZero` for `BigFloat`s only.
"""
struct RoundingMode{T} end

Expand Down Expand Up @@ -76,15 +80,18 @@ const RoundDown = RoundingMode{:Down}()
RoundFromZero
Rounds away from zero.
This rounding mode may only be used with `T == BigFloat` inputs to [`round`](@ref).
!!! compat "Julia 1.9"
`RoundFromZero` requires at least Julia 1.9. Prior versions support
`RoundFromZero` for `BigFloat`s only.
# Examples
```jldoctest
julia> BigFloat("1.0000000000000001", 5, RoundFromZero)
1.06
```
"""
const RoundFromZero = RoundingMode{:FromZero}() # mpfr only
const RoundFromZero = RoundingMode{:FromZero}()

"""
RoundNearestTiesAway
Expand Down
23 changes: 13 additions & 10 deletions test/int.jl
Expand Up @@ -352,25 +352,28 @@ end
@testset "rounding division" begin
for x = -100:100
for y = 1:100
for rnd in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp)
for rnd in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundFromZero)
@test div(x,y,rnd) == round(x/y,rnd)
@test div(x,-y,rnd) == round(x/-y,rnd)
end
@test divrem(x,y,RoundFromZero) == (div(x,y,RoundFromZero), rem(x,y,RoundFromZero))
@test divrem(x,-y,RoundFromZero) == (div(x,-y,RoundFromZero), rem(x,-y,RoundFromZero))
end
end
for (a, b, nearest, away, up) in (
(3, 2, 2, 2, 2),
(5, 3, 2, 2, 2),
(-3, 2, -2, -2, -1),
(5, 2, 2, 3, 3),
(-5, 2, -2, -3, -2),
(-5, 3, -2, -2, -2),
(5, -3, -2, -2, -2))
for (a, b, nearest, away, up, from_zero) in (
(3, 2, 2, 2, 2, 2),
(5, 3, 2, 2, 2, 2),
(-3, 2, -2, -2, -1, -2),
(5, 2, 2, 3, 3, 3),
(-5, 2, -2, -3, -2, -3),
(-5, 3, -2, -2, -2, -2),
(5, -3, -2, -2, -2, -2))
for sign in (+1, -1)
(a, b) = (a*sign, b*sign)
@test div(a, b, RoundNearest) === nearest
@test div(a, b, RoundNearestTiesAway) === away
@test div(a, b, RoundNearestTiesUp) === up
@test div(a, b, RoundFromZero) === from_zero
end
end

Expand All @@ -381,7 +384,7 @@ end
@test div(typemax(Int)-2, typemax(Int), RoundNearest) === 1

# Exhaustively test (U)Int8 to catch any overflow-style issues
for r in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp)
for r in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundFromZero)
for T in (UInt8, Int8)
for x in typemin(T):typemax(T)
for y in typemin(T):typemax(T)
Expand Down
13 changes: 9 additions & 4 deletions test/numbers.jl
Expand Up @@ -2519,29 +2519,34 @@ end
@test rem(T(1), T(2), RoundNearest) == 1
@test rem(T(1), T(2), RoundDown) == 1
@test rem(T(1), T(2), RoundUp) == -1
@test rem(T(1), T(2), RoundFromZero) == -1
@test rem(T(1.5), T(2), RoundToZero) == 1.5
@test rem(T(1.5), T(2), RoundNearest) == -0.5
@test rem(T(1.5), T(2), RoundDown) == 1.5
@test rem(T(1.5), T(2), RoundUp) == -0.5
@test rem(T(1.5), T(2), RoundFromZero) == -0.5
@test rem(T(-1), T(2), RoundToZero) == -1
@test rem(T(-1), T(2), RoundNearest) == -1
@test rem(T(-1), T(2), RoundDown) == 1
@test rem(T(-1), T(2), RoundUp) == -1
@test rem(T(-1), T(2), RoundFromZero) == 1
@test rem(T(-1.5), T(2), RoundToZero) == -1.5
@test rem(T(-1.5), T(2), RoundNearest) == 0.5
@test rem(T(-1.5), T(2), RoundDown) == 0.5
@test rem(T(-1.5), T(2), RoundUp) == -1.5
for mode in [RoundToZero, RoundNearest, RoundDown, RoundUp]
@test rem(T(-1.5), T(2), RoundFromZero) == 0.5
for mode in [RoundToZero, RoundNearest, RoundDown, RoundUp, RoundFromZero]
@test isnan(rem(T(1), T(0), mode))
@test isnan(rem(T(Inf), T(2), mode))
@test isnan(rem(T(1), T(NaN), mode))
# FIXME: The broken case erroneously returns -Inf
@test rem(T(4), floatmin(T) * 2, mode) == 0 broken=(T == BigFloat && mode == RoundUp)
@test rem(T(4), floatmin(T) * 2, mode) == 0 broken=(T == BigFloat && mode in (RoundUp,RoundFromZero))
end
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundToZero), -0.0)
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundNearest), -0.0)
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundDown), 0.0)
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundUp), 0.0)
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundDown), 0.0)
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundUp), 0.0)
@test isequal(rem(nextfloat(typemin(T)), T(2), RoundFromZero), 0.0)
end

@testset "rem for $T RoundNearest" for T in (Int8, Int16, Int32, Int64, Int128)
Expand Down
15 changes: 15 additions & 0 deletions test/rounding.jl
Expand Up @@ -128,6 +128,16 @@ end
else
@test u === r
end

r = round(u, RoundFromZero)
if isfinite(u)
@test isfinite(r)
@test isinteger(r)
@test signbit(u) ? (r == floor(u)) : (r == ceil(u))
@test signbit(u) == signbit(r)
else
@test u === r
end
end
end
end
Expand Down Expand Up @@ -171,6 +181,7 @@ end
@test round.(y) t[(i+1+isodd(i>>2))>>2 for i in r]
@test broadcast(x -> round(x, RoundNearestTiesAway), y) t[(i+1+(i>=0))>>2 for i in r]
@test broadcast(x -> round(x, RoundNearestTiesUp), y) t[(i+2)>>2 for i in r]
@test broadcast(x -> round(x, RoundFromZero), y) t[(i+3*(i>=0))>>2 for i in r]
end
end
end
Expand All @@ -190,6 +201,10 @@ end
@test round(Int,-2.5,RoundNearestTiesUp) == -2
@test round(Int,-1.5,RoundNearestTiesUp) == -1
@test round(Int,-1.9) == -2
@test round(Int,nextfloat(1.0),RoundFromZero) == 2
@test round(Int,-nextfloat(1.0),RoundFromZero) == -2
@test round(Int,prevfloat(1.0),RoundFromZero) == 1
@test round(Int,-prevfloat(1.0),RoundFromZero) == -1
@test_throws InexactError round(Int64, 9.223372036854776e18)
@test round(Int64, 9.223372036854775e18) == 9223372036854774784
@test_throws InexactError round(Int64, -9.223372036854778e18)
Expand Down

0 comments on commit c2da085

Please sign in to comment.