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

Refactor rounding (including bugfixes) and document API #50812

Merged
merged 19 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ include(strcat((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) #
# numeric operations
include("hashing.jl")
include("rounding.jl")
using .Rounding
include("div.jl")
include("rawbigints.jl")
include("float.jl")
Expand Down
10 changes: 5 additions & 5 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,8 @@ TypeError(where, @nospecialize(expected::Type), @nospecialize(got)) =
TypeError(Symbol(where), "", expected, got)
struct InexactError <: Exception
func::Symbol
T # Type
val
InexactError(f::Symbol, @nospecialize(T), @nospecialize(val)) = (@noinline; new(f, T, val))
args
InexactError(f::Symbol, @nospecialize(args...)) = (@noinline; new(f, args))
end
struct OverflowError <: Exception
msg::AbstractString
Expand Down Expand Up @@ -630,8 +629,6 @@ eval(Core, :(NamedTuple{names,T}(args::T) where {names, T <: Tuple} =

import .Intrinsics: eq_int, trunc_int, lshr_int, sub_int, shl_int, bitcast, sext_int, zext_int, and_int

throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} = (@noinline; throw(InexactError(f, T, val)))

function is_top_bit_set(x)
@inline
eq_int(trunc_int(UInt8, lshr_int(x, sub_int(shl_int(sizeof(x), 3), 1))), trunc_int(UInt8, 1))
Expand All @@ -642,6 +639,9 @@ function is_top_bit_set(x::Union{Int8,UInt8})
eq_int(lshr_int(x, 7), trunc_int(typeof(x), 1))
end

#TODO delete this function (but see #48097):
throw_inexacterror(args...) = throw(InexactError(args...))

function check_top_bit(::Type{To}, x) where {To}
@inline
is_top_bit_set(x) && throw_inexacterror(:check_top_bit, To, x)
Expand Down
6 changes: 4 additions & 2 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,10 @@ end

function showerror(io::IO, ex::InexactError)
print(io, "InexactError: ", ex.func, '(')
nameof(ex.T) === ex.func || print(io, ex.T, ", ")
print(io, ex.val, ')')
T = first(ex.args)
nameof(T) === ex.func || print(io, T, ", ")
join(io, ex.args[2:end], ", ")
print(io, ")")
Experimental.show_error_hints(io, ex)
end

Expand Down
32 changes: 13 additions & 19 deletions base/float.jl
Original file line number Diff line number Diff line change
Expand Up @@ -436,21 +436,15 @@ unsafe_trunc(::Type{UInt128}, x::Float16) = unsafe_trunc(UInt128, Float32(x))
unsafe_trunc(::Type{Int128}, x::Float16) = unsafe_trunc(Int128, Float32(x))

# matches convert methods
# also determines floor, ceil, round
trunc(::Type{Signed}, x::IEEEFloat) = trunc(Int,x)
trunc(::Type{Unsigned}, x::IEEEFloat) = trunc(UInt,x)
trunc(::Type{Integer}, x::IEEEFloat) = trunc(Int,x)

# Bool
trunc(::Type{Bool}, x::AbstractFloat) = (-1 < x < 2) ? 1 <= x : throw(InexactError(:trunc, Bool, x))
floor(::Type{Bool}, x::AbstractFloat) = (0 <= x < 2) ? 1 <= x : throw(InexactError(:floor, Bool, x))
ceil(::Type{Bool}, x::AbstractFloat) = (-1 < x <= 1) ? 0 < x : throw(InexactError(:ceil, Bool, x))
round(::Type{Bool}, x::AbstractFloat) = (-0.5 <= x < 1.5) ? 0.5 < x : throw(InexactError(:round, Bool, x))

round(x::IEEEFloat, r::RoundingMode{:ToZero}) = trunc_llvm(x)
round(x::IEEEFloat, r::RoundingMode{:Down}) = floor_llvm(x)
round(x::IEEEFloat, r::RoundingMode{:Up}) = ceil_llvm(x)
round(x::IEEEFloat, r::RoundingMode{:Nearest}) = rint_llvm(x)
# also determines trunc, floor, ceil
round(::Type{Signed}, x::IEEEFloat, r::RoundingMode) = round(Int, x, r)
round(::Type{Unsigned}, x::IEEEFloat, r::RoundingMode) = round(UInt, x, r)
round(::Type{Integer}, x::IEEEFloat, r::RoundingMode) = round(Int, x, r)

round(x::IEEEFloat, ::RoundingMode{:ToZero}) = trunc_llvm(x)
round(x::IEEEFloat, ::RoundingMode{:Down}) = floor_llvm(x)
round(x::IEEEFloat, ::RoundingMode{:Up}) = ceil_llvm(x)
round(x::IEEEFloat, ::RoundingMode{:Nearest}) = rint_llvm(x)

## floating point promotions ##
promote_rule(::Type{Float32}, ::Type{Float16}) = Float32
Expand Down Expand Up @@ -931,11 +925,11 @@ for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UIn
# directly. `Tf(typemax(Ti))+1` is either always exactly representable, or
# rounded to `Inf` (e.g. when `Ti==UInt128 && Tf==Float32`).
@eval begin
function trunc(::Type{$Ti},x::$Tf)
function round(::Type{$Ti},x::$Tf,::RoundingMode{:ToZero})
if $(Tf(typemin(Ti))-one(Tf)) < x < $(Tf(typemax(Ti))+one(Tf))
return unsafe_trunc($Ti,x)
else
throw(InexactError(:trunc, $Ti, x))
throw(InexactError(:round, $Ti, x, RoundToZero))
end
end
function (::Type{$Ti})(x::$Tf)
Expand All @@ -955,11 +949,11 @@ for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UIn
# be rounded up. This assumes that `Tf(typemin(Ti)) > -Inf`, which is true for
# these types, but not for `Float16` or larger integer types.
@eval begin
function trunc(::Type{$Ti},x::$Tf)
function round(::Type{$Ti},x::$Tf,::RoundingMode{:ToZero})
if $(Tf(typemin(Ti))) <= x < $(Tf(typemax(Ti)))
return unsafe_trunc($Ti,x)
else
throw(InexactError(:trunc, $Ti, x))
throw(InexactError(:round, $Ti, x, RoundToZero))
end
end
function (::Type{$Ti})(x::$Tf)
Expand Down
19 changes: 0 additions & 19 deletions base/floatfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ isinteger(x::AbstractFloat) = (x - trunc(x) == 0)

# See rounding.jl for docstring.

function round(::Type{T}, x::AbstractFloat, r::RoundingMode) where {T<:Integer}
r != RoundToZero && (x = round(x,r))
trunc(T, x)
end

# NOTE: this relies on the current keyword dispatch behaviour (#9498).
function round(x::Real, r::RoundingMode=RoundNearest;
digits::Union{Nothing,Integer}=nothing, sigdigits::Union{Nothing,Integer}=nothing, base::Union{Nothing,Integer}=nothing)
Expand All @@ -77,20 +72,6 @@ function round(x::Real, r::RoundingMode=RoundNearest;
end
end

trunc(x::Real; kwargs...) = round(x, RoundToZero; kwargs...)
floor(x::Real; kwargs...) = round(x, RoundDown; kwargs...)
ceil(x::Real; kwargs...) = round(x, RoundUp; kwargs...)

# fallbacks
trunc(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundToZero; kwargs...)
floor(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundDown; kwargs...)
ceil(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundUp; kwargs...)
round(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundNearest; kwargs...)

round(::Type{T}, x::Real, r::RoundingMode) where {T} = convert(T, round(x, r))

round(x::Integer, r::RoundingMode) = x

# round x to multiples of 1/invstep
function _round_invstep(x, invstep, r::RoundingMode)
y = round(x * invstep, r) / invstep
Expand Down
2 changes: 1 addition & 1 deletion base/gmp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ function BigInt(x::Float64)
unsafe_trunc(BigInt,x)
end

function trunc(::Type{BigInt}, x::Union{Float16,Float32,Float64})
function round(::Type{BigInt}, x::Union{Float16,Float32,Float64}, r::RoundingMode{:ToZero})
isfinite(x) || throw(InexactError(:trunc, BigInt, x))
unsafe_trunc(BigInt,x)
end
Expand Down
64 changes: 0 additions & 64 deletions base/int.jl
Original file line number Diff line number Diff line change
Expand Up @@ -629,70 +629,6 @@ mod(x::Integer, ::Type{T}) where {T<:Integer} = rem(x, T)

unsafe_trunc(::Type{T}, x::Integer) where {T<:Integer} = rem(x, T)

"""
trunc([T,] x)
trunc(x; digits::Integer= [, base = 10])
trunc(x; sigdigits::Integer= [, base = 10])

`trunc(x)` returns the nearest integral value of the same type as `x` whose absolute value
is less than or equal to the absolute value of `x`.

`trunc(T, x)` converts the result to type `T`, throwing an `InexactError` if the truncated
value is not representable a `T`.

Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).

See also: [`%`](@ref rem), [`floor`](@ref), [`unsigned`](@ref), [`unsafe_trunc`](@ref).

# Examples
```jldoctest
julia> trunc(2.22)
2.0

julia> trunc(-2.22, digits=1)
-2.2

julia> trunc(Int, -2.22)
-2
```
"""
function trunc end

"""
floor([T,] x)
floor(x; digits::Integer= [, base = 10])
floor(x; sigdigits::Integer= [, base = 10])

`floor(x)` returns the nearest integral value of the same type as `x` that is less than or
equal to `x`.

`floor(T, x)` converts the result to type `T`, throwing an `InexactError` if the floored
value is not representable a `T`.

Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
"""
function floor end

"""
ceil([T,] x)
ceil(x; digits::Integer= [, base = 10])
ceil(x; sigdigits::Integer= [, base = 10])

`ceil(x)` returns the nearest integral value of the same type as `x` that is greater than or
equal to `x`.

`ceil(T, x)` converts the result to type `T`, throwing an `InexactError` if the ceiled
value is not representable as a `T`.

Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
"""
function ceil end

round(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
trunc(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
floor(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
ceil(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)

## integer construction ##

"""
Expand Down
13 changes: 0 additions & 13 deletions base/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,6 @@ round(::Type{T}, x::Real, r::RoundingMode=RoundNearest) where {T>:Missing} = rou
round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T>:Missing,Tr} = round(nonmissingtype_checked(T), x, r)
round(::Type{T}, x::Rational{Bool}, r::RoundingMode=RoundNearest) where {T>:Missing} = round(nonmissingtype_checked(T), x, r)

# Handle ceil, floor, and trunc separately as they have no RoundingMode argument
for f in (:(ceil), :(floor), :(trunc))
@eval begin
($f)(::Missing; sigdigits::Integer=0, digits::Integer=0, base::Integer=0) = missing
($f)(::Type{>:Missing}, ::Missing) = missing
($f)(::Type{T}, ::Missing) where {T} = throw(MissingException(missing_conversion_msg(T)))
($f)(::Type{T}, x::Any) where {T>:Missing} = $f(nonmissingtype_checked(T), x)
# to fix ambiguities
($f)(::Type{T}, x::Rational) where {T>:Missing} = $f(nonmissingtype_checked(T), x)
($f)(::Type{T}, x::Real) where {T>:Missing} = $f(nonmissingtype_checked(T), x)
end
end

# to avoid ambiguity warnings
(^)(::Missing, ::Integer) = missing

Expand Down
11 changes: 4 additions & 7 deletions base/mpfr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,15 @@ round(::Type{T}, x::BigFloat, r::RoundingMode) where T<:Union{Signed, Unsigned}
invoke(round, Tuple{Type{<:Union{Signed, Unsigned}}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, T, x, r)
round(::Type{BigInt}, x::BigFloat, r::RoundingMode) =
invoke(round, Tuple{Type{BigInt}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, BigInt, x, r)
round(::Type{<:Integer}, x::BigFloat, r::RoundingMode) = throw(MethodError(round, (Integer, x, r)))


unsafe_trunc(::Type{T}, x::BigFloat) where {T<:Integer} = unsafe_trunc(T, _unchecked_cast(T, x, RoundToZero))
unsafe_trunc(::Type{BigInt}, x::BigFloat) = _unchecked_cast(BigInt, x, RoundToZero)

# TODO: Ideally the base fallbacks for these would already exist
for (f, rnd) in zip((:trunc, :floor, :ceil, :round),
(RoundToZero, RoundDown, RoundUp, :(ROUNDING_MODE[])))
@eval $f(::Type{T}, x::BigFloat) where T<:Union{Unsigned, Signed, BigInt} = round(T, x, $rnd)
@eval $f(::Type{Integer}, x::BigFloat) = $f(BigInt, x)
end
round(::Type{T}, x::BigFloat) where T<:Integer = round(T, x, ROUNDING_MODE[])
# these two methods are split to increase their precedence in disambiguation:
round(::Type{Integer}, x::BigFloat, r::RoundingMode) = round(BigInt, x, r)
round(::Type{Integer}, x::BigFloat, r::MPFRRoundingMode) = round(BigInt, x, r)

function Bool(x::BigFloat)
iszero(x) && return false
Expand Down
85 changes: 83 additions & 2 deletions base/rounding.jl
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ function _convert_rounding(::Type{T}, x::Real, r::RoundingMode{:ToZero}) where T
end
end

# Default definitions

"""
set_zero_subnormals(yes::Bool) -> Bool

Expand Down Expand Up @@ -313,8 +315,8 @@ for IEEE arithmetic, and `true` if they might be converted to zeros.
get_zero_subnormals() = ccall(:jl_get_zero_subnormals,Int32,())!=0

end #module
using .Rounding

# Docstring listed here so it appears above the complex docstring.
"""
round([T,] x, [r::RoundingMode])
round(x, [r::RoundingMode]; digits::Integer=0, base = 10)
Expand Down Expand Up @@ -388,4 +390,83 @@ julia> round(357.913; sigdigits=4, base=2)

To extend `round` to new numeric types, it is typically sufficient to define `Base.round(x::NewType, r::RoundingMode)`.
"""
round(T::Type, x)
function round end

"""
trunc([T,] x)
trunc(x; digits::Integer= [, base = 10])
trunc(x; sigdigits::Integer= [, base = 10])

`trunc(x)` returns the nearest integral value of the same type as `x` whose absolute value
is less than or equal to the absolute value of `x`.

`trunc(T, x)` converts the result to type `T`, throwing an `InexactError` if the truncated
value is not representable a `T`.

Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).

To support `trunc` for a new type, define `Base.round(x::NewType, ::RoundingMode{:ToZero})`.

See also: [`%`](@ref rem), [`floor`](@ref), [`unsigned`](@ref), [`unsafe_trunc`](@ref).

# Examples
```jldoctest
julia> trunc(2.22)
2.0

julia> trunc(-2.22, digits=1)
-2.2

julia> trunc(Int, -2.22)
-2
```
"""
function trunc end

"""
floor([T,] x)
floor(x; digits::Integer= [, base = 10])
floor(x; sigdigits::Integer= [, base = 10])

`floor(x)` returns the nearest integral value of the same type as `x` that is less than or
equal to `x`.

`floor(T, x)` converts the result to type `T`, throwing an `InexactError` if the floored
value is not representable a `T`.

Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).

To support `floor` for a new type, define `Base.round(x::NewType, ::RoundingMode{:Down})`.
"""
function floor end

"""
ceil([T,] x)
ceil(x; digits::Integer= [, base = 10])
ceil(x; sigdigits::Integer= [, base = 10])

`ceil(x)` returns the nearest integral value of the same type as `x` that is greater than or
equal to `x`.

`ceil(T, x)` converts the result to type `T`, throwing an `InexactError` if the ceiled
value is not representable as a `T`.

Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).

To support `ceil` for a new type, define `Base.round(x::NewType, ::RoundingMode{:Up})`.
"""
function ceil end

trunc(x; kws...) = round(x, RoundToZero; kws...)
floor(x; kws...) = round(x, RoundDown; kws...)
ceil(x; kws...) = round(x, RoundUp; kws...)
round(x; kws...) = round(x, RoundNearest; kws...)

trunc(::Type{T}, x) where T = round(T, x, RoundToZero)
floor(::Type{T}, x) where T = round(T, x, RoundDown)
ceil(::Type{T}, x) where T = round(T, x, RoundUp)
round(::Type{T}, x) where T = round(T, x, RoundNearest)

round(::Type{T}, x, r::RoundingMode) where T = convert(T, round(x, r))

round(x::Integer, r::RoundingMode) = x
2 changes: 1 addition & 1 deletion doc/src/base/math.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Base.exp10
Base.Math.ldexp
Base.Math.modf
Base.expm1
Base.round(::Type, ::Any)
Base.round
Base.Rounding.RoundingMode
Base.Rounding.RoundNearest
Base.Rounding.RoundNearestTiesAway
Expand Down
Loading