-
-
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
isqrt is a mess #4884
Comments
This feels like one of those cases where you can use Float64 to approximate and then patch up the answer quickly if it's not quite correct. |
The computation |
I wonder if you can use Newton's method, operating on Int128, to get within ±n for some small n, and then just exhaustively search. |
The following seems to work, though it may be suboptimal:
|
I can't prove that it's correct, but a single iteration of Newton's method with integer ops works in all cases I've tried: function isqrt(x::Int128)
s = convert(Int128,trunc(sqrt(x)))
(s + div(x,s)) >> 1
end |
You're right, the "fixup" loops in my version never seem to be needed. I wonder why it's not off by one occasionally? |
I think you're always close enough from the floating-point approximation that a single Newton step gets you there. |
Ok, this is incredibly hand-wavy at best, but Newton's method for sqrt converges quadratically, so every iteration should double the number of accurate digits. Since the floating-point computation gives 52 correct bits, that means that one more iteration should give 104 correct bits. Since the largest possible sqrt for a 128-bit integer only requires 64 bits, this suffices. Of course, that's some pretty vigorous handwaving. |
I was also about to handwave the same way :) Anyhow, loops or assert would be nice anyway to ensure that wrong results are never returned no matter what (<- simple paranoia). BTW, the "(s+1)(s+1) <= x" check won't work near typemax(Int128), because (s+1)(s+1) wraps around to negatives. "(s+1)*(s+1)-x <= 0" is better. |
I know an exact Newton iteration would suffice. I was worried more about the round off errors in the integer divisions, but upon reflection I think those can be bounded to show that the final answer can't be off by more than one. |
Or rather, that the final answer is off by less than 1. |
Btw, isqrt is wrong for julia> isqrt(9223372030926249000)^2
9223372030926249001 The same trick fixes it: julia> function Base.isqrt(x::Int64)
s = convert(Int64,trunc(sqrt(x)))
(s + div(x,s)) >> 1
end
isqrt (generic function with 4 methods)
julia> isqrt(9223372030926249000)^2
9223372024852248004
julia> isqrt(9223372030926249000)
3037000498 |
I would think we can do something cheaper for Int64... |
Probably, although if it's more than adding the result of a comparison, it's probably not much cheaper. |
Here's an off by one example. julia> a = int128(typemax(Int64))
9223372036854775807
julia> isqrt(a*a-1)
9223372036854775807 In general x*x-y for large x and small y are susceptible to having isqrt off by one by the current implementation. |
Yup, good find. That's definitely a problem. Looks like 128-bit at least may need two Newton iterations. |
You don't need another Newton iteration to correct an off-by-one error. |
Perhaps not, but I'm finding all sorts of problems with these now that I'm looking harder. |
Perhaps you can get a more systematic sense of what's needed by taking a random sample and looking at how many Newton iterations it takes for the solution to change by less than (say) 0.1. |
Throwing Newton iterations at this problem does not solve anything since they won't converge for x^2-1 numbers. The off by one solution x is moved to x-1, but another Newton iteration takes you back to x. To make matters worse isqrt is currently rather ugly in the small end: julia> isqrt(3)
2
julia> isqrt(0)
ERROR: integer division error
in isqrt at intfuncs.jl:317 |
Yes, the former |
It's not that far off. Obviously 0 needs special casing and negative values could do with a more specific domain error than the one thrown from the sqrt call. Otherwise it's just an off by one correction that's needed. Some of the handwaving above can be tightened up by the following observation:
Even quite crude bounds on the relative error e from the trunc(sqrt(x)) computation in double precision are sufficient to prove that sqrt(x) <= y2 <= sqrt(x) + 1 in exact mathematics and taking the integer divisions into account that sqrt(x) - 1 <= y2 <= sqrt(x) + 1. |
The assumption should be e>-1, obviously, so that 1+e doesn't switch sign. |
* upstream/master: (89 commits) fix JuliaLang#5225 update pcre fix off-by-1 in isqrt. closes JuliaLang#4884 Add more keywords to ctags regex, plus README annotate the types of arguments for derived trigonometric & hyperbolic functions fix doc for && and || and update helpdb only show ccall literal address warning in imaging mode. closes JuliaLang#5215 minor update of hypot to ensure consistency of output types Fix JuliaLang#5217 silence compiler warning hopefully more robust way of getting github URL (don't assume module name is Pkg name) add text/html writemime for MethodList and Method (fix JuliaLang#4952) update NEWS doc: `import M: single,name` syntax, close JuliaLang#5214 clean up native finalizers code specialized abs2 for bool remove use of callback API in REPL Some error message cleanup to fix segfault when transposing sparse vector with illegal values. test/git*.jl: don't use `echo` to read-and-write from processes. test/git*.jl: don't use `echo` to read-and-write from processes. ...
The
isqrt
function is poorly documented, but a reasonable definition ofisqrt(n)
would be the largest integerm
such thatm*m <= n
. This coincides with the definition:that we are using, more or less.
However, for
Int128
values, thesqrt
function converts to aFloat64
, which discards too many bits for the result to be accurate. e.g.gives
1
rather than0
(and there are other numbers where the difference is larger). A simple fix would be to useBigFloat
with sufficient precision forisqrt(x::Int128)
, although I'm not sure if there is a more efficient solution (or whether we care).The text was updated successfully, but these errors were encountered: