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

RFC: RoundNearest argument for rem #10946

Merged
merged 7 commits into from Jan 20, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions base/exports.jl
Expand Up @@ -397,6 +397,7 @@ export
reim,
reinterpret,
rem,
rem2pi,
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we removed rem1, and does rem2pi need to be exported?

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

🤷‍♂️

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Honestly, I'm not sure. I'm also not sure why rem2pi takes a rounding argument but mod2pi doesn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mod2pi(x) is rem2pi(x, RoundDown)

round,
sec,
secd,
Expand Down
6 changes: 3 additions & 3 deletions base/linalg/diagonal.jl
Expand Up @@ -262,9 +262,9 @@ diag(D::Diagonal) = D.diag
trace(D::Diagonal) = sum(D.diag)
det(D::Diagonal) = prod(D.diag)
logdet{T<:Real}(D::Diagonal{T}) = sum(log, D.diag)
function logdet{T<:Complex}(D::Diagonal{T}) #Make sure branch cut is correct
x = sum(log, D.diag)
-pi<imag(x)<pi ? x : real(x)+(mod2pi(imag(x)+pi)-pi)*im
function logdet{T<:Complex}(D::Diagonal{T}) # make sure branch cut is correct
z = sum(log, D.diag)
complex(real(z), rem2pi(imag(z), RoundNearest))
end
# identity matrices via eye(Diagonal{type},n)
eye{T}(::Type{Diagonal{T}}, n::Int) = Diagonal(ones(T,n))
Expand Down
201 changes: 170 additions & 31 deletions base/math.jl
Expand Up @@ -13,7 +13,7 @@ export sin, cos, tan, sinh, cosh, tanh, asin, acos, atan,
cbrt, sqrt, erf, erfc, erfcx, erfi, dawson,
significand,
lgamma, hypot, gamma, lfact, max, min, minmax, ldexp, frexp,
clamp, clamp!, modf, ^, mod2pi,
clamp, clamp!, modf, ^, mod2pi, rem2pi,
airyai, airyaiprime, airybi, airybiprime,
airyaix, airyaiprimex, airybix, airybiprimex,
besselj0, besselj1, besselj, besseljx,
Expand All @@ -25,7 +25,7 @@ export sin, cos, tan, sinh, cosh, tanh, asin, acos, atan,

import Base: log, exp, sin, cos, tan, sinh, cosh, tanh, asin,
acos, atan, asinh, acosh, atanh, sqrt, log2, log10,
max, min, minmax, ^, exp2, muladd,
max, min, minmax, ^, exp2, muladd, rem,
exp10, expm1, log1p,
sign_mask, exponent_mask, exponent_one, exponent_half,
significand_mask, significand_bits, exponent_bits, exponent_bias
Expand Down Expand Up @@ -619,6 +619,42 @@ function frexp{T<:AbstractFloat}(x::T)
return reinterpret(T, xu), k
end

"""
rem(x, y, r::RoundingMode)

Compute the remainder of `x` after integer division by `y`, with the quotient rounded
according to the rounding mode `r`. In other words, the quantity

x - y*round(x/y,r)

without any intermediate rounding.

- if `r == RoundNearest`, then the result is exact, and in the interval
``[-|y|/2, |y|/2]``.

- if `r == RoundToZero` (default), then the result is exact, and in the interval
``[0, |y|)`` if `x` is positive, or ``(-|y|, 0]`` otherwise.

- if `r == RoundDown`, then the result is in the interval ``[0, y)`` if `y` is positive, or
``(y, 0]`` otherwise. The result may not be exact if `x` and `y` have different signs, and
`abs(x) < abs(y)`.

- if `r == RoundUp`, 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)`.

"""
rem(x, y, ::RoundingMode{:ToZero}) = rem(x,y)
rem(x, y, ::RoundingMode{:Down}) = mod(x,y)
rem(x, y, ::RoundingMode{:Up}) = mod(x,-y)

rem(x::Float64, y::Float64, ::RoundingMode{:Nearest}) =
ccall((:remainder, libm),Float64,(Float64,Float64),x,y)
rem(x::Float32, y::Float32, ::RoundingMode{:Nearest}) =
ccall((:remainderf, libm),Float32,(Float32,Float32),x,y)



"""
modf(x)

Expand Down Expand Up @@ -666,7 +702,7 @@ function angle_restrict_symm(theta)
return r
end

## mod2pi-related calculations ##
## rem2pi-related calculations ##

function add22condh(xh::Float64, xl::Float64, yh::Float64, yl::Float64)
# as above, but only compute and return high double
Expand Down Expand Up @@ -709,63 +745,166 @@ const pi4o2_h = 6.283185307179586 # convert(Float64, pi * BigFloat(2))
const pi4o2_l = 2.4492935982947064e-16 # convert(Float64, pi * BigFloat(2) - pi4o2_h)

"""
mod2pi(x)
rem2pi(x, r::RoundingMode)

Modulus after division by `2π`, returning in the range ``[0,2π)``.
Compute the remainder of `x` after integer division by `2π`, with the quotient rounded
according to the rounding mode `r`. In other words, the quantity

This function computes a floating point representation of the modulus after division by
numerically exact `2π`, and is therefore not exactly the same as `mod(x,2π)`, which would
compute the modulus of `x` relative to division by the floating-point number `2π`.
x - 2π*round(x/(2π),r)

without any intermediate rounding. This internally uses a high precision approximation of
2π, and so will give a more accurate result than `rem(x,2π,r)`

- if `r == RoundNearest`, then the result is in the interval ``[-π, π]``. This will generally
be the most accurate result.

- if `r == RoundToZero`, then the result is in the interval ``[0, 2π]`` if `x` is positive,.
or ``[-2π, 0]`` otherwise.

- if `r == RoundDown`, then the result is in the interval ``[0, 2π]``.

- if `r == RoundUp`, then the result is in the interval ``[-2π, 0]``.

```jldoctest
julia> mod2pi(9*pi/4)
0.7853981633974481
julia> rem2pi(7pi/4, RoundNearest)
-0.7853981633974485

julia> rem2pi(7pi/4, RoundDown)
5.497787143782138
```
"""
function mod2pi(x::Float64) # or modtau(x)
# with r = mod2pi(x)
# a) 0 <= r < 2π (note: boundary open or closed - a bit fuzzy, due to rem_pio2 implementation)
# b) r-x = k*2π with k integer
function rem2pi end
function rem2pi(x::Float64, ::RoundingMode{:Nearest})
abs(x) < pi && return x

(n,y) = ieee754_rem_pio2(x)

# note: mod(n,4) is 0,1,2,3; while mod(n-1,4)+1 is 1,2,3,4.
# We use the latter to push negative y in quadrant 0 into the positive (one revolution, + 4*pi/2)
if iseven(n)
if n & 2 == 2 # n % 4 == 2: add/subtract pi
if y[1] <= 0
return add22condh(y[1],y[2],pi2o2_h,pi2o2_l)
else
return add22condh(y[1],y[2],-pi2o2_h,-pi2o2_l)
end
else # n % 4 == 0: add 0
return y[1]
end
else
if n & 2 == 2 # n % 4 == 3: subtract pi/2
return add22condh(y[1],y[2],-pi1o2_h,-pi1o2_l)
else # n % 4 == 1: add pi/2
return add22condh(y[1],y[2],pi1o2_h,pi1o2_l)
end
end
end
function rem2pi(x::Float64, ::RoundingMode{:ToZero})
ax = abs(x)
ax <= 2*Float64(pi,RoundDown) && return x

(n,y) = ieee754_rem_pio2(ax)

if iseven(n)
if n & 2 == 2 # n % 4 == 2: add pi
z = add22condh(y[1],y[2],pi2o2_h,pi2o2_l)
else # n % 4 == 0: add 0 or 2pi
if y[1] > 0
z = y[1]
else # negative: add 2pi
z = add22condh(y[1],y[2],pi4o2_h,pi4o2_l)
end
end
else
if n & 2 == 2 # n % 4 == 3: add 3pi/2
z = add22condh(y[1],y[2],pi3o2_h,pi3o2_l)
else # n % 4 == 1: add pi/2
z = add22condh(y[1],y[2],pi1o2_h,pi1o2_l)
end
end
copysign(z,x)
end
function rem2pi(x::Float64, ::RoundingMode{:Down})
if x < pi4o2_h
if 0.0 <= x return x end
if x > -pi4o2_h
if x >= 0
return x
elseif x > -pi4o2_h
return add22condh(x,0.0,pi4o2_h,pi4o2_l)
end
end

(n,y) = ieee754_rem_pio2(x)

if iseven(n)
if n & 2 == 2 # add pi
if n & 2 == 2 # n % 4 == 2: add pi
return add22condh(y[1],y[2],pi2o2_h,pi2o2_l)
else # add 0 or 2pi
if y[1] > 0.0
else # n % 4 == 0: add 0 or 2pi
if y[1] > 0
return y[1]
else # else add 2pi
else # negative: add 2pi
return add22condh(y[1],y[2],pi4o2_h,pi4o2_l)
end
end
else # add pi/2 or 3pi/2
if n & 2 == 2 # add 3pi/2
else
if n & 2 == 2 # n % 4 == 3: add 3pi/2
return add22condh(y[1],y[2],pi3o2_h,pi3o2_l)
else # add pi/2
else # n % 4 == 1: add pi/2
return add22condh(y[1],y[2],pi1o2_h,pi1o2_l)
end
end
end
function rem2pi(x::Float64, ::RoundingMode{:Up})
if x > -pi4o2_h
if x <= 0
return x
elseif x < pi4o2_h
return add22condh(x,0.0,-pi4o2_h,-pi4o2_l)
end
end

(n,y) = ieee754_rem_pio2(x)

mod2pi(x::Float32) = Float32(mod2pi(Float64(x)))
mod2pi(x::Int32) = mod2pi(Float64(x))
function mod2pi(x::Int64)
fx = Float64(x)
fx == x || throw(ArgumentError("Int64 argument to mod2pi is too large: $x"))
mod2pi(fx)
if iseven(n)
if n & 2 == 2 # n % 4 == 2: sub pi
return add22condh(y[1],y[2],-pi2o2_h,-pi2o2_l)
else # n % 4 == 0: sub 0 or 2pi
if y[1] < 0
return y[1]
else # positive: sub 2pi
return add22condh(y[1],y[2],-pi4o2_h,-pi4o2_l)
end
end
else
if n & 2 == 2 # n % 4 == 3: sub pi/2
return add22condh(y[1],y[2],-pi1o2_h,-pi1o2_l)
else # n % 4 == 1: sub 3pi/2
return add22condh(y[1],y[2],-pi3o2_h,-pi3o2_l)
end
end
end

rem2pi(x::Float32, r::RoundingMode) = Float32(rem2pi(Float64(x), r))
rem2pi(x::Int32, r::RoundingMode) = rem2pi(Float64(x), r)
function rem2pi(x::Int64, r::RoundingMode)
fx = Float64(x)
fx == x || throw(ArgumentError("Int64 argument to rem2pi is too large: $x"))
rem2pi(fx, r)
end

"""
mod2pi(x)

Modulus after division by `2π`, returning in the range ``[0,2π)``.

This function computes a floating point representation of the modulus after division by
numerically exact `2π`, and is therefore not exactly the same as `mod(x,2π)`, which would
compute the modulus of `x` relative to division by the floating-point number `2π`.

```jldoctest
julia> mod2pi(9*pi/4)
0.7853981633974481
```
"""
mod2pi(x) = rem2pi(x,RoundDown)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@StefanKarpinski mod2pi is now just a special case of rem2pi


# generic fallback; for number types, promotion.jl does promotion

"""
Expand Down
11 changes: 10 additions & 1 deletion base/mpfr.jl
Expand Up @@ -12,7 +12,7 @@ import
bessely0, bessely1, ceil, cmp, convert, copysign, div,
exp, exp2, exponent, factorial, floor, fma, hypot, isinteger,
isfinite, isinf, isnan, ldexp, log, log2, log10, max, min, mod, modf,
nextfloat, prevfloat, promote_rule, rem, round, show,
nextfloat, prevfloat, promote_rule, rem, rem2pi, round, show,
sum, sqrt, string, print, trunc, precision, exp10, expm1,
gamma, lgamma, digamma, erf, erfc, zeta, eta, log1p, airyai,
eps, signbit, sin, cos, tan, sec, csc, cot, acos, asin, atan,
Expand Down Expand Up @@ -656,6 +656,15 @@ function rem(x::BigFloat, y::BigFloat)
return z
end

function rem(x::BigFloat, y::BigFloat, ::RoundingMode{:Nearest})
z = BigFloat()
ccall((:mpfr_remainder, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
return z
end

# TODO: use a higher-precision BigFloat(pi) here?
rem2pi(x::BigFloat, r::RoundingMode) = rem(x, 2*BigFloat(pi), r)

function sum(arr::AbstractArray{BigFloat})
z = BigFloat(0)
for i in arr
Expand Down
3 changes: 2 additions & 1 deletion doc/src/stdlib/math.md
Expand Up @@ -16,8 +16,9 @@ Base.div
Base.fld
Base.cld
Base.mod
Base.Math.mod2pi
Base.rem
Base.rem2pi
Base.Math.mod2pi
Base.divrem
Base.fldmod
Base.fld1
Expand Down