diff --git a/NEWS.md b/NEWS.md index a3ace5297c974..c13a9d0b65823 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/base/div.jl b/base/div.jl index a2f7a39eb7053..7b172ecc95a63 100644 --- a/base/div.jl +++ b/base/div.jl @@ -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) @@ -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) @@ -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; @@ -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) @@ -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) @@ -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), diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index d1164005d3e44..f06fdd0f3cec9 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -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]) diff --git a/base/rounding.jl b/base/rounding.jl index bf29d8b54602e..25cfe2dc09829 100644 --- a/base/rounding.jl +++ b/base/rounding.jl @@ -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 @@ -76,7 +80,10 @@ 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 @@ -84,7 +91,7 @@ julia> BigFloat("1.0000000000000001", 5, RoundFromZero) 1.06 ``` """ -const RoundFromZero = RoundingMode{:FromZero}() # mpfr only +const RoundFromZero = RoundingMode{:FromZero}() """ RoundNearestTiesAway diff --git a/test/int.jl b/test/int.jl index 436222ecf0e2d..d7b79fb6c1e0c 100644 --- a/test/int.jl +++ b/test/int.jl @@ -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 @@ -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) diff --git a/test/numbers.jl b/test/numbers.jl index 674595c185f3f..e85f62e3cb65a 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -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) diff --git a/test/rounding.jl b/test/rounding.jl index e4c51212e81fa..0fe1513c6c450 100644 --- a/test/rounding.jl +++ b/test/rounding.jl @@ -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 @@ -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 @@ -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)