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

Combine signif into round, use keyword args for digits/sigdigits #26670

Merged
merged 20 commits into from
Apr 6, 2018
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
10 changes: 7 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,8 +634,9 @@ Library improvements
* `IOBuffer` can take the `sizehint` keyword argument to suggest a capacity of
the buffer ([#25944]).

* `trunc`, `floor`, `ceil`, `round`, and `signif` specify `base` using a
keyword argument. ([#26156])
* `trunc`, `floor`, `ceil`, and `round` specify `digits`, `sigdigits` and `base` using
keyword arguments. ([#26156], [#26670])


Compiler/Runtime improvements
-----------------------------
Expand Down Expand Up @@ -1134,6 +1135,8 @@ Deprecated or removed
* `isupper`, `islower`, `ucfirst` and `lcfirst` have been deprecated in favor of `isuppercase`,
`islowercase`, `uppercasefirst` and `lowercasefirst`, respectively ([#26442]).

* `signif` has been deprecated in favor of the `sigdigits` keyword argument to `round`.

Command-line option changes
---------------------------

Expand Down Expand Up @@ -1442,4 +1445,5 @@ Command-line option changes
[#26286]: https://github.com/JuliaLang/julia/issues/26286
[#26436]: https://github.com/JuliaLang/julia/issues/26436
[#26442]: https://github.com/JuliaLang/julia/issues/26442
[#26600]: https://github.com/JuliaLang/julia/issues/26600
[#26600]: https://github.com/JuliaLang/julia/issues/26600
[#26670]: https://github.com/JuliaLang/julia/issues/26670
15 changes: 6 additions & 9 deletions base/complex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,9 @@ atanh(z::Complex) = atanh(float(z))
#Rounding complex numbers
#Requires two different RoundingModes for the real and imaginary components
"""
round(z, RoundingModeReal, RoundingModeImaginary)
round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]])
round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]; digits=, base=10)
round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]; sigdigits=, base=10)

Return the nearest integral value of the same type as the complex-valued `z` to `z`,
breaking ties using the specified [`RoundingMode`](@ref)s. The first
Expand All @@ -954,16 +956,11 @@ julia> round(3.14 + 4.5im)
3.0 + 4.0im
```
"""
function round(z::Complex{<:AbstractFloat}, ::RoundingMode{MR}, ::RoundingMode{MI}) where {MR,MI}
Complex(round(real(z), RoundingMode{MR}()),
round(imag(z), RoundingMode{MI}()))
function round(z::Complex, rr::RoundingMode=RoundNearest, ri::RoundingMode=rr; digits=nothing, sigdigits=nothing, base=10)
Complex(round(real(z), rr; digits=digits, sigdigits=sigdigits, base=base),
round(imag(z), ri; digits=digits, sigdigits=sigdigits, base=base))
end
round(z::Complex) = Complex(round(real(z)), round(imag(z)))

function round(z::Complex, digits::Integer; base::Integer = 10)
Complex(round(real(z), digits, base = base),
round(imag(z), digits, base = base))
end

float(z::Complex{<:AbstractFloat}) = z
float(z::Complex) = Complex(float(real(z)), float(imag(z)))
Expand Down
16 changes: 11 additions & 5 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1502,11 +1502,17 @@ end
false)

# PR 26156
@deprecate trunc(x, digits, base) trunc(x, digits, base = base)
@deprecate floor(x, digits, base) floor(x, digits, base = base)
@deprecate ceil(x, digits, base) ceil(x, digits, base = base)
@deprecate round(x, digits, base) round(x, digits, base = base)
@deprecate signif(x, digits, base) signif(x, digits, base = base)
@deprecate trunc(x::Number, digits) trunc(x; digits=digits)
@deprecate floor(x::Number, digits) floor(x; digits=digits)
@deprecate ceil(x::Number, digits) ceil(x; digits=digits)
@deprecate round(x::Number, digits) round(x; digits=digits)
@deprecate signif(x::Number, digits) round(x; sigdigits=digits, base = base)

@deprecate trunc(x::Number, digits, base) trunc(x; digits=digits, base = base)
@deprecate floor(x::Number, digits, base) floor(x; digits=digits, base = base)
@deprecate ceil(x::Number, digits, base) ceil(x; digits=digits, base = base)
@deprecate round(x::Number, digits, base) round(x; digits=digits, base = base)
@deprecate signif(x::Number, digits, base) round(x; sigdigits=digits, base = base)

# issue #25965
@deprecate spawn(cmds::AbstractCmd) run(cmds, wait = false)
Expand Down
1 change: 0 additions & 1 deletion base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ export
sign,
signbit,
signed,
signif,
significand,
sin,
sinc,
Expand Down
36 changes: 17 additions & 19 deletions base/float.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,28 +348,26 @@ trunc(::Type{Integer}, x::Float64) = trunc(Int,x)
trunc(::Type{T}, x::Float16) where {T<:Integer} = trunc(T, Float32(x))

# fallbacks
floor(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,floor(x))
floor(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,_round(x, RoundDown))
floor(::Type{T}, x::Float16) where {T<:Integer} = floor(T, Float32(x))
ceil(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,ceil(x))
ceil(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,_round(x, RoundUp))
ceil(::Type{T}, x::Float16) where {T<:Integer} = ceil(T, Float32(x))
round(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,round(x))
round(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,_round(x, RoundNearest))
round(::Type{T}, x::Float16) where {T<:Integer} = round(T, Float32(x))

trunc(x::Float64) = trunc_llvm(x)
trunc(x::Float32) = trunc_llvm(x)
trunc(x::Float16) = Float16(trunc(Float32(x)))
_round(x::Float64, r::RoundingMode{:ToZero}) = trunc_llvm(x)
_round(x::Float32, r::RoundingMode{:ToZero}) = trunc_llvm(x)
_round(x::Float64, r::RoundingMode{:Down}) = floor_llvm(x)
_round(x::Float32, r::RoundingMode{:Down}) = floor_llvm(x)
_round(x::Float64, r::RoundingMode{:Up}) = ceil_llvm(x)
_round(x::Float32, r::RoundingMode{:Up}) = ceil_llvm(x)
_round(x::Float64, r::RoundingMode{:Nearest}) = rint_llvm(x)
_round(x::Float32, r::RoundingMode{:Nearest}) = rint_llvm(x)

floor(x::Float64) = floor_llvm(x)
floor(x::Float32) = floor_llvm(x)
floor(x::Float16) = Float16(floor(Float32(x)))

ceil(x::Float64) = ceil_llvm(x)
ceil(x::Float32) = ceil_llvm(x)
ceil(x::Float16) = Float16( ceil(Float32(x)))

round(x::Float64) = rint_llvm(x)
round(x::Float32) = rint_llvm(x)
round(x::Float16) = Float16(round(Float32(x)))
_round(x::Float16, r::RoundingMode{:ToZero}) = Float16(_round(Float32(x), r))
_round(x::Float16, r::RoundingMode{:Down}) = Float16(_round(Float32(x), r))
_round(x::Float16, r::RoundingMode{:Up}) = Float16(_round(Float32(x), r))
_round(x::Float16, r::RoundingMode{:Nearest}) = Float16(_round(Float32(x), r))

## floating point promotions ##
promote_rule(::Type{Float32}, ::Type{Float16}) = Float32
Expand Down Expand Up @@ -662,7 +660,7 @@ for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UIn
end
end
function (::Type{$Ti})(x::$Tf)
if ($(Tf(typemin(Ti))) <= x <= $(Tf(typemax(Ti)))) && (trunc(x) == x)
if ($(Tf(typemin(Ti))) <= x <= $(Tf(typemax(Ti)))) && (_round(x, RoundToZero) == x)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious: why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problems with bootstrapping: trunc(x) had to be defined later.

return unsafe_trunc($Ti,x)
else
throw(InexactError($(Expr(:quote,Ti.name.name)), $Ti, x))
Expand All @@ -683,7 +681,7 @@ for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UIn
end
end
function (::Type{$Ti})(x::$Tf)
if ($(Tf(typemin(Ti))) <= x < $(Tf(typemax(Ti)))) && (trunc(x) == x)
if ($(Tf(typemin(Ti))) <= x < $(Tf(typemax(Ti)))) && (_round(x, RoundToZero) == x)
return unsafe_trunc($Ti,x)
else
throw(InexactError($(Expr(:quote,Ti.name.name)), $Ti, x))
Expand Down
194 changes: 101 additions & 93 deletions base/floatfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,43 +44,51 @@ isinteger(x::AbstractFloat) = (x - trunc(x) == 0)

"""
round([T,] x, [r::RoundingMode])
round(x, [digits; base = 10])
round(x, [r::RoundingMode]; digits::Integer=0, base = 10)
round(x, [r::RoundingMode]; sigdigits::Integer, base = 10)

Rounds `x` to an integer value according to the provided
[`RoundingMode`](@ref), returning a value of the same type as `x`. When not
specifying a rounding mode the global mode will be used
(see [`rounding`](@ref)), which by default is round to the nearest integer
([`RoundNearest`](@ref) mode), with ties (fractional values of 0.5) being
rounded to the nearest even integer.
Rounds the number `x`.

Without keyword arguments, `x` is rounded to an integer value, returning a value of type
`T`, or of the same type of `x` if no `T` is provided. An [`InexactError`](@ref) will be
thrown if the value is not representable by `T`, similar to [`convert`](@ref).

If the `digits` keyword argument is provided, it rounds to the specified number of digits
after the decimal place (or before if negative), in base `base`.

If the `sigdigits` keyword argument is provided, it rounds to the specified number of
significant digits, in base `base`.

The [`RoundingMode`](@ref) `r` controls the direction of the rounding; the default is
[`RoundNearest`](@ref), which rounds to the nearest integer, with ties (fractional values
of 0.5) being rounded to the nearest even integer. Note that `round` may give incorrect
results if the global rounding mode is changed (see [`rounding`](@ref)).

# Examples
```jldoctest
julia> round(1.7)
2.0

julia> round(Int, 1.7)
2

julia> round(1.5)
2.0

julia> round(2.5)
2.0
```

The optional [`RoundingMode`](@ref) argument will change how the number gets
rounded.

`round(T, x, [r::RoundingMode])` converts the result to type `T`, throwing an
[`InexactError`](@ref) if the value is not representable.

`round(x, digits)` rounds to the specified number of digits after the decimal place (or
before if negative). `round(x, digits, base = base)` rounds using a base other than 10.

# Examples
```jldoctest
julia> round(pi, 2)
julia> round(pi; digits=2)
3.14

julia> round(pi, 3, base = 2)
julia> round(pi; digits=3, base=2)
3.125

julia> round(123.456; sigdigits=2)
120.0

julia> round(357.913; sigdigits=4, base=2)
352.0
```

!!! note
Expand All @@ -104,93 +112,93 @@ julia> round(pi, 3, base = 2)
1.2
```

See also [`signif`](@ref) for rounding to significant digits.
# Extensions

To extend `round` to new numeric types, it is typically sufficient to define `Base._round(x::NewType, ::RoundingMode)`.
"""
round(T::Type, x)
round(x::Real, ::RoundingMode{:ToZero}) = trunc(x)
round(x::Real, ::RoundingMode{:Up}) = ceil(x)
round(x::Real, ::RoundingMode{:Down}) = floor(x)
# C-style round
function round(x::AbstractFloat, ::RoundingMode{:NearestTiesAway})
y = trunc(x)
ifelse(x==y,y,trunc(2*x-y))

round(::Type{T}, x::AbstractFloat, r::RoundingMode{:ToZero}) where {T<:Integer} = trunc(T, x)
round(::Type{T}, x::AbstractFloat, r::RoundingMode) where {T<:Integer} = trunc(T, _round(x,r))

function round(x::Real, r::RoundingMode=RoundNearest;
digits::Union{Nothing,Integer}=nothing, sigdigits::Union{Nothing,Integer}=nothing, base=10)
isfinite(x) || return x
_round(x,r,digits,sigdigits,base)
end
# Java-style round
function round(x::AbstractFloat, ::RoundingMode{:NearestTiesUp})
y = floor(x)
ifelse(x==y,y,copysign(floor(2*x-y),x))
trunc(x::Real; kwargs...) = round(x, RoundToZero; kwargs...)
floor(x::Real; kwargs...) = round(x, RoundDown; kwargs...)
ceil(x::Real; kwargs...) = round(x, RoundUp; kwargs...)

_round(x, r::RoundingMode, digits::Nothing, sigdigits::Nothing, base) = _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
if !isfinite(y)
return x
end
return y
end

# round x to multiples of step
function _round_step(x, step, r::RoundingMode)
# TODO: use div with rounding mode
y = _round(x / step, r) * step
if !isfinite(y)
if x > 0
return (r == RoundUp ? oftype(x, Inf) : zero(x))
elseif x < 0
return (r == RoundDown ? -oftype(x, Inf) : -zero(x))
else
return x
end
end
return y
end
round(::Type{T}, x::AbstractFloat, r::RoundingMode) where {T<:Integer} = trunc(T,round(x,r))

# adapted from Matlab File Exchange roundsd: http:https://www.mathworks.com/matlabcentral/fileexchange/26212
# for round, og is the power of 10 relative to the decimal point
# for signif, og is the absolute power of 10
# digits and base must be integers, x must be convertable to float
function _round(x, r::RoundingMode, digits::Integer, sigdigits::Nothing, base)
fx = float(x)
if digits >= 0
invstep = oftype(fx, base)^digits
_round_invstep(fx, invstep, r)
else
step = oftype(fx, base)^-digits
_round_step(fx, step, r)
end
end

function _signif_og(x, digits, base)
hidigit(x::Integer, base) = ndigits0z(x, base)
function hidigit(x::Real, base)
iszero(x) && return 0
fx = float(x)
if base == 10
e = floor(log10(abs(x)) - digits + 1.)
og = oftype(x, exp10(abs(e)))
return 1 + floor(Int, log10(abs(fx)))
elseif base == 2
e = exponent(abs(x)) - digits + 1.
og = oftype(x, exp2(abs(e)))
return 1 + exponent(x)
else
e = floor(log(base, abs(x)) - digits + 1.)
og = oftype(x, float(base) ^ abs(e))
return 1 + floor(Int, log(base, abs(fx)))
end
return og, e
end

"""
signif(x, digits; base = 10)

Rounds (in the sense of [`round`](@ref)) `x` so that there are `digits` significant digits, under a
base `base` representation, default 10.
function _round(x, r::RoundingMode, digits::Nothing, sigdigits::Integer, base)
h = hidigit(x, base)
_round(x, r, sigdigits-h, nothing, base)
end

# Examples
```jldoctest
julia> signif(123.456, 2)
120.0
_round(x, r::RoundingMode, digits::Integer, sigdigits::Integer, base) =
throw(ArgumentError("`round` cannot use both `digits` and `sigdigits` arguments."))

julia> signif(357.913, 4, base = 2)
352.0
```
"""
function signif(x::Real, digits::Integer; base::Integer = 10)
digits < 1 && throw(DomainError(digits, "`digits` cannot be less than 1."))

x = float(x)
(x == 0 || !isfinite(x)) && return x
og, e = _signif_og(x, digits, base)
if e >= 0 # for numeric stability
r = round(x/og)*og
else
r = round(x*og)/og
end
!isfinite(r) ? x : r
# C-style round
function _round(x::AbstractFloat, ::RoundingMode{:NearestTiesAway})
y = trunc(x)
ifelse(x==y,y,trunc(2*x-y))
end

for f in (:round, :ceil, :floor, :trunc)
@eval begin
function ($f)(x::Real, digits::Integer; base::Integer = 10)
x = float(x)
og = convert(eltype(x),base)^digits
r = ($f)(x * og) / og

if !isfinite(r)
if digits > 0
return x
elseif x > 0
return $(:ceil == f ? :(convert(eltype(x), Inf)) : :(zero(x)))
elseif x < 0
return $(:floor == f ? :(-convert(eltype(x), Inf)) : :(-zero(x)))
else
return x
end
end
return r
end
end
# Java-style round
function _round(x::AbstractFloat, ::RoundingMode{:NearestTiesUp})
y = floor(x)
ifelse(x==y,y,copysign(floor(2*x-y),x))
end

# isapprox: approximate equality of numbers
Expand Down
Loading