-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Changes from 3 commits
1b84e72
b0bf8b4
99fc8b8
bc9ae48
9ac63f4
86e78c8
a653e7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -397,6 +397,7 @@ export | |
reim, | ||
reinterpret, | ||
rem, | ||
rem2pi, | ||
round, | ||
sec, | ||
secd, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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 | ||
|
@@ -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 | ||
`[-abs(y)/2, abs(y)/2]`. | ||
|
||
- if `r == RoundToZero` (default), then the result is exact, and in the interval | ||
`[0,abs(y))` if `x` is positive, or `(abs(y),0]` otherwise. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe math-mode (double backtick) the intervals since that isn't Julia notation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. |
||
|
||
- 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) | ||
|
||
|
@@ -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 | ||
|
@@ -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 `rem(x,2π,r)` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a more ... accurate result than ? |
||
|
||
- 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
- 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @StefanKarpinski |
||
|
||
# generic fallback; for number types, promotion.jl does promotion | ||
|
||
""" | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤷♂️
There was a problem hiding this comment.
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 butmod2pi
doesn't.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mod2pi(x)
isrem2pi(x, RoundDown)