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

Round from zero support for non-BigFloats #41246

Merged
merged 14 commits into from Apr 12, 2022
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)
stevengj marked this conversation as resolved.
Show resolved Hide resolved
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.8"
stevengj marked this conversation as resolved.
Show resolved Hide resolved
`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.8"
`RoundFromZero` requires at least Julia 1.8. Prior versions support
stevengj marked this conversation as resolved.
Show resolved Hide resolved
`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.8"
`RoundFromZero` requires at least Julia 1.8. Prior versions support
stevengj marked this conversation as resolved.
Show resolved Hide resolved
`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
11 changes: 8 additions & 3 deletions test/numbers.jl
Expand Up @@ -2519,19 +2519,23 @@ 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))
Expand All @@ -2540,8 +2544,9 @@ end
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