Skip to content

Commit e0f5247

Browse files
authored
Refactor rounding (including bugfixes) and document API (#50812)
1 parent a3e2316 commit e0f5247

File tree

15 files changed

+200
-136
lines changed

15 files changed

+200
-136
lines changed

base/Base.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ include(strcat((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) #
222222
# numeric operations
223223
include("hashing.jl")
224224
include("rounding.jl")
225-
using .Rounding
226225
include("div.jl")
227226
include("rawbigints.jl")
228227
include("float.jl")

base/boot.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,8 @@ TypeError(where, @nospecialize(expected::Type), @nospecialize(got)) =
328328
TypeError(Symbol(where), "", expected, got)
329329
struct InexactError <: Exception
330330
func::Symbol
331-
T # Type
332-
val
333-
InexactError(f::Symbol, @nospecialize(T), @nospecialize(val)) = (@noinline; new(f, T, val))
331+
args
332+
InexactError(f::Symbol, @nospecialize(args...)) = (@noinline; new(f, args))
334333
end
335334
struct OverflowError <: Exception
336335
msg::AbstractString
@@ -630,8 +629,6 @@ eval(Core, :(NamedTuple{names,T}(args::T) where {names, T <: Tuple} =
630629

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

633-
throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} = (@noinline; throw(InexactError(f, T, val)))
634-
635632
function is_top_bit_set(x)
636633
@inline
637634
eq_int(trunc_int(UInt8, lshr_int(x, sub_int(shl_int(sizeof(x), 3), 1))), trunc_int(UInt8, 1))
@@ -642,6 +639,9 @@ function is_top_bit_set(x::Union{Int8,UInt8})
642639
eq_int(lshr_int(x, 7), trunc_int(typeof(x), 1))
643640
end
644641

642+
#TODO delete this function (but see #48097):
643+
throw_inexacterror(args...) = throw(InexactError(args...))
644+
645645
function check_top_bit(::Type{To}, x) where {To}
646646
@inline
647647
is_top_bit_set(x) && throw_inexacterror(:check_top_bit, To, x)

base/errorshow.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,10 @@ end
173173

174174
function showerror(io::IO, ex::InexactError)
175175
print(io, "InexactError: ", ex.func, '(')
176-
nameof(ex.T) === ex.func || print(io, ex.T, ", ")
177-
print(io, ex.val, ')')
176+
T = first(ex.args)
177+
nameof(T) === ex.func || print(io, T, ", ")
178+
join(io, ex.args[2:end], ", ")
179+
print(io, ")")
178180
Experimental.show_error_hints(io, ex)
179181
end
180182

base/float.jl

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -436,21 +436,15 @@ unsafe_trunc(::Type{UInt128}, x::Float16) = unsafe_trunc(UInt128, Float32(x))
436436
unsafe_trunc(::Type{Int128}, x::Float16) = unsafe_trunc(Int128, Float32(x))
437437

438438
# matches convert methods
439-
# also determines floor, ceil, round
440-
trunc(::Type{Signed}, x::IEEEFloat) = trunc(Int,x)
441-
trunc(::Type{Unsigned}, x::IEEEFloat) = trunc(UInt,x)
442-
trunc(::Type{Integer}, x::IEEEFloat) = trunc(Int,x)
443-
444-
# Bool
445-
trunc(::Type{Bool}, x::AbstractFloat) = (-1 < x < 2) ? 1 <= x : throw(InexactError(:trunc, Bool, x))
446-
floor(::Type{Bool}, x::AbstractFloat) = (0 <= x < 2) ? 1 <= x : throw(InexactError(:floor, Bool, x))
447-
ceil(::Type{Bool}, x::AbstractFloat) = (-1 < x <= 1) ? 0 < x : throw(InexactError(:ceil, Bool, x))
448-
round(::Type{Bool}, x::AbstractFloat) = (-0.5 <= x < 1.5) ? 0.5 < x : throw(InexactError(:round, Bool, x))
449-
450-
round(x::IEEEFloat, r::RoundingMode{:ToZero}) = trunc_llvm(x)
451-
round(x::IEEEFloat, r::RoundingMode{:Down}) = floor_llvm(x)
452-
round(x::IEEEFloat, r::RoundingMode{:Up}) = ceil_llvm(x)
453-
round(x::IEEEFloat, r::RoundingMode{:Nearest}) = rint_llvm(x)
439+
# also determines trunc, floor, ceil
440+
round(::Type{Signed}, x::IEEEFloat, r::RoundingMode) = round(Int, x, r)
441+
round(::Type{Unsigned}, x::IEEEFloat, r::RoundingMode) = round(UInt, x, r)
442+
round(::Type{Integer}, x::IEEEFloat, r::RoundingMode) = round(Int, x, r)
443+
444+
round(x::IEEEFloat, ::RoundingMode{:ToZero}) = trunc_llvm(x)
445+
round(x::IEEEFloat, ::RoundingMode{:Down}) = floor_llvm(x)
446+
round(x::IEEEFloat, ::RoundingMode{:Up}) = ceil_llvm(x)
447+
round(x::IEEEFloat, ::RoundingMode{:Nearest}) = rint_llvm(x)
454448

455449
## floating point promotions ##
456450
promote_rule(::Type{Float32}, ::Type{Float16}) = Float32
@@ -931,11 +925,11 @@ for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UIn
931925
# directly. `Tf(typemax(Ti))+1` is either always exactly representable, or
932926
# rounded to `Inf` (e.g. when `Ti==UInt128 && Tf==Float32`).
933927
@eval begin
934-
function trunc(::Type{$Ti},x::$Tf)
928+
function round(::Type{$Ti},x::$Tf,::RoundingMode{:ToZero})
935929
if $(Tf(typemin(Ti))-one(Tf)) < x < $(Tf(typemax(Ti))+one(Tf))
936930
return unsafe_trunc($Ti,x)
937931
else
938-
throw(InexactError(:trunc, $Ti, x))
932+
throw(InexactError(:round, $Ti, x, RoundToZero))
939933
end
940934
end
941935
function (::Type{$Ti})(x::$Tf)
@@ -955,11 +949,11 @@ for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UIn
955949
# be rounded up. This assumes that `Tf(typemin(Ti)) > -Inf`, which is true for
956950
# these types, but not for `Float16` or larger integer types.
957951
@eval begin
958-
function trunc(::Type{$Ti},x::$Tf)
952+
function round(::Type{$Ti},x::$Tf,::RoundingMode{:ToZero})
959953
if $(Tf(typemin(Ti))) <= x < $(Tf(typemax(Ti)))
960954
return unsafe_trunc($Ti,x)
961955
else
962-
throw(InexactError(:trunc, $Ti, x))
956+
throw(InexactError(:round, $Ti, x, RoundToZero))
963957
end
964958
end
965959
function (::Type{$Ti})(x::$Tf)

base/floatfuncs.jl

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ isinteger(x::AbstractFloat) = (x - trunc(x) == 0)
4646

4747
# See rounding.jl for docstring.
4848

49-
function round(::Type{T}, x::AbstractFloat, r::RoundingMode) where {T<:Integer}
50-
r != RoundToZero && (x = round(x,r))
51-
trunc(T, x)
52-
end
53-
5449
# NOTE: this relies on the current keyword dispatch behaviour (#9498).
5550
function round(x::Real, r::RoundingMode=RoundNearest;
5651
digits::Union{Nothing,Integer}=nothing, sigdigits::Union{Nothing,Integer}=nothing, base::Union{Nothing,Integer}=nothing)
@@ -77,20 +72,6 @@ function round(x::Real, r::RoundingMode=RoundNearest;
7772
end
7873
end
7974

80-
trunc(x::Real; kwargs...) = round(x, RoundToZero; kwargs...)
81-
floor(x::Real; kwargs...) = round(x, RoundDown; kwargs...)
82-
ceil(x::Real; kwargs...) = round(x, RoundUp; kwargs...)
83-
84-
# fallbacks
85-
trunc(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundToZero; kwargs...)
86-
floor(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundDown; kwargs...)
87-
ceil(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundUp; kwargs...)
88-
round(::Type{T}, x::Real; kwargs...) where {T} = round(T, x, RoundNearest; kwargs...)
89-
90-
round(::Type{T}, x::Real, r::RoundingMode) where {T} = convert(T, round(x, r))
91-
92-
round(x::Integer, r::RoundingMode) = x
93-
9475
# round x to multiples of 1/invstep
9576
function _round_invstep(x, invstep, r::RoundingMode)
9677
y = round(x * invstep, r) / invstep

base/gmp.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ function BigInt(x::Float64)
320320
unsafe_trunc(BigInt,x)
321321
end
322322

323-
function trunc(::Type{BigInt}, x::Union{Float16,Float32,Float64})
323+
function round(::Type{BigInt}, x::Union{Float16,Float32,Float64}, r::RoundingMode{:ToZero})
324324
isfinite(x) || throw(InexactError(:trunc, BigInt, x))
325325
unsafe_trunc(BigInt,x)
326326
end

base/int.jl

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -629,70 +629,6 @@ mod(x::Integer, ::Type{T}) where {T<:Integer} = rem(x, T)
629629

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

632-
"""
633-
trunc([T,] x)
634-
trunc(x; digits::Integer= [, base = 10])
635-
trunc(x; sigdigits::Integer= [, base = 10])
636-
637-
`trunc(x)` returns the nearest integral value of the same type as `x` whose absolute value
638-
is less than or equal to the absolute value of `x`.
639-
640-
`trunc(T, x)` converts the result to type `T`, throwing an `InexactError` if the truncated
641-
value is not representable a `T`.
642-
643-
Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
644-
645-
See also: [`%`](@ref rem), [`floor`](@ref), [`unsigned`](@ref), [`unsafe_trunc`](@ref).
646-
647-
# Examples
648-
```jldoctest
649-
julia> trunc(2.22)
650-
2.0
651-
652-
julia> trunc(-2.22, digits=1)
653-
-2.2
654-
655-
julia> trunc(Int, -2.22)
656-
-2
657-
```
658-
"""
659-
function trunc end
660-
661-
"""
662-
floor([T,] x)
663-
floor(x; digits::Integer= [, base = 10])
664-
floor(x; sigdigits::Integer= [, base = 10])
665-
666-
`floor(x)` returns the nearest integral value of the same type as `x` that is less than or
667-
equal to `x`.
668-
669-
`floor(T, x)` converts the result to type `T`, throwing an `InexactError` if the floored
670-
value is not representable a `T`.
671-
672-
Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
673-
"""
674-
function floor end
675-
676-
"""
677-
ceil([T,] x)
678-
ceil(x; digits::Integer= [, base = 10])
679-
ceil(x; sigdigits::Integer= [, base = 10])
680-
681-
`ceil(x)` returns the nearest integral value of the same type as `x` that is greater than or
682-
equal to `x`.
683-
684-
`ceil(T, x)` converts the result to type `T`, throwing an `InexactError` if the ceiled
685-
value is not representable as a `T`.
686-
687-
Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
688-
"""
689-
function ceil end
690-
691-
round(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
692-
trunc(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
693-
floor(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
694-
ceil(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x)
695-
696632
## integer construction ##
697633

698634
"""

base/missing.jl

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,19 +150,6 @@ round(::Type{T}, x::Real, r::RoundingMode=RoundNearest) where {T>:Missing} = rou
150150
round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T>:Missing,Tr} = round(nonmissingtype_checked(T), x, r)
151151
round(::Type{T}, x::Rational{Bool}, r::RoundingMode=RoundNearest) where {T>:Missing} = round(nonmissingtype_checked(T), x, r)
152152

153-
# Handle ceil, floor, and trunc separately as they have no RoundingMode argument
154-
for f in (:(ceil), :(floor), :(trunc))
155-
@eval begin
156-
($f)(::Missing; sigdigits::Integer=0, digits::Integer=0, base::Integer=0) = missing
157-
($f)(::Type{>:Missing}, ::Missing) = missing
158-
($f)(::Type{T}, ::Missing) where {T} = throw(MissingException(missing_conversion_msg(T)))
159-
($f)(::Type{T}, x::Any) where {T>:Missing} = $f(nonmissingtype_checked(T), x)
160-
# to fix ambiguities
161-
($f)(::Type{T}, x::Rational) where {T>:Missing} = $f(nonmissingtype_checked(T), x)
162-
($f)(::Type{T}, x::Real) where {T>:Missing} = $f(nonmissingtype_checked(T), x)
163-
end
164-
end
165-
166153
# to avoid ambiguity warnings
167154
(^)(::Missing, ::Integer) = missing
168155

base/mpfr.jl

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -380,18 +380,15 @@ round(::Type{T}, x::BigFloat, r::RoundingMode) where T<:Union{Signed, Unsigned}
380380
invoke(round, Tuple{Type{<:Union{Signed, Unsigned}}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, T, x, r)
381381
round(::Type{BigInt}, x::BigFloat, r::RoundingMode) =
382382
invoke(round, Tuple{Type{BigInt}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, BigInt, x, r)
383-
round(::Type{<:Integer}, x::BigFloat, r::RoundingMode) = throw(MethodError(round, (Integer, x, r)))
384383

385384

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

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

396393
function Bool(x::BigFloat)
397394
iszero(x) && return false

base/rounding.jl

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ function _convert_rounding(::Type{T}, x::Real, r::RoundingMode{:ToZero}) where T
283283
end
284284
end
285285

286+
# Default definitions
287+
286288
"""
287289
set_zero_subnormals(yes::Bool) -> Bool
288290
@@ -313,8 +315,8 @@ for IEEE arithmetic, and `true` if they might be converted to zeros.
313315
get_zero_subnormals() = ccall(:jl_get_zero_subnormals,Int32,())!=0
314316

315317
end #module
318+
using .Rounding
316319

317-
# Docstring listed here so it appears above the complex docstring.
318320
"""
319321
round([T,] x, [r::RoundingMode])
320322
round(x, [r::RoundingMode]; digits::Integer=0, base = 10)
@@ -388,4 +390,83 @@ julia> round(357.913; sigdigits=4, base=2)
388390
389391
To extend `round` to new numeric types, it is typically sufficient to define `Base.round(x::NewType, r::RoundingMode)`.
390392
"""
391-
round(T::Type, x)
393+
function round end
394+
395+
"""
396+
trunc([T,] x)
397+
trunc(x; digits::Integer= [, base = 10])
398+
trunc(x; sigdigits::Integer= [, base = 10])
399+
400+
`trunc(x)` returns the nearest integral value of the same type as `x` whose absolute value
401+
is less than or equal to the absolute value of `x`.
402+
403+
`trunc(T, x)` converts the result to type `T`, throwing an `InexactError` if the truncated
404+
value is not representable a `T`.
405+
406+
Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
407+
408+
To support `trunc` for a new type, define `Base.round(x::NewType, ::RoundingMode{:ToZero})`.
409+
410+
See also: [`%`](@ref rem), [`floor`](@ref), [`unsigned`](@ref), [`unsafe_trunc`](@ref).
411+
412+
# Examples
413+
```jldoctest
414+
julia> trunc(2.22)
415+
2.0
416+
417+
julia> trunc(-2.22, digits=1)
418+
-2.2
419+
420+
julia> trunc(Int, -2.22)
421+
-2
422+
```
423+
"""
424+
function trunc end
425+
426+
"""
427+
floor([T,] x)
428+
floor(x; digits::Integer= [, base = 10])
429+
floor(x; sigdigits::Integer= [, base = 10])
430+
431+
`floor(x)` returns the nearest integral value of the same type as `x` that is less than or
432+
equal to `x`.
433+
434+
`floor(T, x)` converts the result to type `T`, throwing an `InexactError` if the floored
435+
value is not representable a `T`.
436+
437+
Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
438+
439+
To support `floor` for a new type, define `Base.round(x::NewType, ::RoundingMode{:Down})`.
440+
"""
441+
function floor end
442+
443+
"""
444+
ceil([T,] x)
445+
ceil(x; digits::Integer= [, base = 10])
446+
ceil(x; sigdigits::Integer= [, base = 10])
447+
448+
`ceil(x)` returns the nearest integral value of the same type as `x` that is greater than or
449+
equal to `x`.
450+
451+
`ceil(T, x)` converts the result to type `T`, throwing an `InexactError` if the ceiled
452+
value is not representable as a `T`.
453+
454+
Keywords `digits`, `sigdigits` and `base` work as for [`round`](@ref).
455+
456+
To support `ceil` for a new type, define `Base.round(x::NewType, ::RoundingMode{:Up})`.
457+
"""
458+
function ceil end
459+
460+
trunc(x; kws...) = round(x, RoundToZero; kws...)
461+
floor(x; kws...) = round(x, RoundDown; kws...)
462+
ceil(x; kws...) = round(x, RoundUp; kws...)
463+
round(x; kws...) = round(x, RoundNearest; kws...)
464+
465+
trunc(::Type{T}, x) where T = round(T, x, RoundToZero)
466+
floor(::Type{T}, x) where T = round(T, x, RoundDown)
467+
ceil(::Type{T}, x) where T = round(T, x, RoundUp)
468+
round(::Type{T}, x) where T = round(T, x, RoundNearest)
469+
470+
round(::Type{T}, x, r::RoundingMode) where T = convert(T, round(x, r))
471+
472+
round(x::Integer, r::RoundingMode) = x

0 commit comments

Comments
 (0)