Square root
Almost no problem here wants $\sqrt n$ as a number. What they want is a fact about it: where a search can stop, whether a value is a perfect square, which integers lie in a band. The square root is the tool that answers all three, and whether it answers correctly depends on one choice every solution makes, mostly without noticing: take the root in floating point, or take it exactly in integers. This page is about that choice: what each kind of root guarantees, the patterns that make a float root safe, and the inputs where it stops being safe. The exact function itself is covered on math.isqrt, the C library call on sqrt, and the algorithms behind both on integer square root. None of that is repeated here.
Three jobs, three tolerances
Across the problems below, a square root does one of three jobs, and each tolerates error differently.
| job | example | what an error costs |
|---|---|---|
| bound a search | trial division to $\sqrt n$ (3, 46); divisor pairs $a \le \sqrt n$ (21, 179) | too high: wasted iterations; too low: missed answers |
| test for a square | $8n+1$ square ⇔ $n$ triangular (42, 45); integer shortest path (86); $\sigma_2(n)$ square (211) | a wrong answer, with nothing to show it happened |
| delimit a band | the roots of the $n$-digit squares (98) | a value from the wrong band slips in, or a real one drops out |
A bound is the forgiving one. It can be loose upward at the price of a few wasted iterations, so long
as the loop body checks the real condition. Problem 757 is built that way: it takes a float
int(limit ** 0.5) + 2 with deliberate slack, then filters every candidate with exact integer
arithmetic. The + 2 is the whole safety argument. Rounding can move the float root by a unit, and
the slack absorbs that, so the root only has to be roughly right. Problem 173 skips the root
altogether. Its loop guard is 4 * t * (t + 1) <= N, the inequality itself rather than its solved form
$t \le \sqrt{N/4}$, so nothing is ever rounded. Squaring the condition instead of rooting the bound
is the cheapest exact technique there is, and it turns up again on the C side as g * g <= ....
A test is the unforgiving one. A float predicate that answers wrong answers plausibly: the loop goes on, the count comes out one off, and nothing fails.
What a float root actually guarantees
IEEE 754 requires sqrt to be correctly rounded: the result is the double nearest the true root
(see double precision). Python's
math.sqrt, x ** 0.5 on a float, C's sqrt and NumPy's np.sqrt all inherit the guarantee. Two
consequences follow, and the reasoning about float roots comes down to them:
-
A perfect square below $2^{53}$ gives back its root exactly. Both $m^2$ and $m$ are representable, and $m$ is the nearest double to itself. That is why
(8 * n + 1) ** 0.5in problem 42 never rejects a genuine triangular number. -
A non-square can also give back an integer, once the root passes $2^{26}$. Near $m$, the gap between neighbouring doubles is about $m \cdot 2^{-52}$. The true root of $m^2 + 1$ is $m + 1/(2m)$, and once $1/(2m)$ drops below half that gap the result rounds to $m$ itself:
>>> n = 2**52 + 1 # (2**26)**2 + 1, not a square >>> math.sqrt(n).is_integer() True
So the bare float predicate, sqrt(x).is_integer() or x ** 0.5 % 1 == 0, has false
positives starting just past $2^{52}$. Above $2^{53}$ it also has false negatives, because the
argument is rounded before the root is even taken. math.isqrt
shows the matching failure for int(sqrt(n)) as a floor. This is the same edge seen from the
predicate side. Problems 42, 45, 46 and 86 all use the bare form in Python. All four are correct,
because their arguments stay in the millions or billions, and each is correct only for that reason.
Round, then verify in integers
The repair keeps the fast float root and moves the decision into integer arithmetic. The C counterparts here do it almost word for word:
long long r = (long long)round(sqrt((double)x));
return r * r == x; /* the integers decide, not the double */
The float only has to land within one half of the true root, and correct rounding keeps it far
closer than that for any x a long long can hold. A non-square can no longer pass, because
r * r == x is exact. The guard has to compare against the original integer x, though. Problem 86's
C converts the double back with (long long)val and compares against that, which works only
because its val is built from small integers and is therefore exact. If x were wide enough to
round on the way into the double, the comparison would be checking the rounded value against itself.
When the inputs pass $2^{53}$, the float can be off by a whole unit and the fix-up becomes a window.
Problem 211 computes $\sigma_2(n)$, which reaches well past $2^{53}$, takes np.sqrt over the whole
array, and then tries root - 1, root, root + 1 with an exact integer square-and-compare. This
is the vectorised form of round-and-verify. NumPy has no exact integer root (see
math.isqrt), so the float does the bulk work
and a three-wide integer check makes the result exact. The C sibling applies the same window to
sqrtl.
Pairing around the root
Problems 21 and 179 count divisors in pairs $(a, n/a)$ with $a \le \sqrt n$. That raises a second,
non-numerical trap at the root itself: when $n = a^2$, the pair is $(a, a)$ and the divisor gets
counted twice. Both solutions subtract it once. Problem 21's Python does it with
- (n_sqrt if n_sqrt**2 == n else 0), and 179's sieve decrements div_count[a * a]. The correction
depends on the root being exact. If the root came out as $a - 1$, the loop would miss $a$ and the
subtraction would never fire. If it came out as $a + 1$, the square test would fail for a real
square and the double count would stand. A floor that is off by one at a perfect square breaks the
divisor sum both ways. That is why 179's C works out its bound with an exact integer loop (once,
outside the sieve) instead of trusting sqrt at precisely the inputs it is most likely to get wrong.
Where rounding bites
The failures come from the input to the root more often than from the root, so look upstream:
- Bands built from powers of ten. Problem 98 delimits the $n$-digit squares with
ceil(sqrt(10**(n-1)))andfloor(sqrt(10**n - 1)). In Python the10**n - 1is an exact integer, butmath.sqrtconverts it to a float first, and from $n = 16$ on, $10^n - 1$ rounds up to $10^n$. The upper root comes out as $10^{n/2}$, one past the last $n$-digit root, so a $(n+1)$-digit square enters the band. Here $n$ is a word length and never gets near 16. The C version hits the same edge from a different direction:pow(10.0, n) - 1does the subtraction indouble, where the- 1is lost at exactly the same $n$. Exact endpoints areisqrt(10**n - 1)andisqrt(10**(n-1) - 1) + 1. - A float division ahead of the root. Problem 46 tests
((n - p) / 2) ** 0.5, where the/has already made a float before the root is taken. It is exact here only because the operands are small.(n - p) // 2, or a parity check followed byisqrt, keeps the argument an integer. fmodon a root. Problem 45 askssqrt(1 + 24t) % 6 == 5, a perfect-square test and a residue test in one comparison. It is exact while the root is an exactly represented integer, and above $2^{52}$ a non-square that rounds to such an integer passes both halves. Splitting it asr = isqrt(x); r * r == x and r % 6 == 5costs nothing and has no ceiling.- A bound that goes stale. Problem 3 recomputes
int(remaining ** 0.5)every time it strips a factor. That is right, since the cofactor shrinks and so should the bound, but the recomputation is also a float root of a value that started at the size of the input. With a 64-bit input, the first few bounds sit exactly in the range where the floor can be off by one.
How to reason about it
- Name the job before writing the root. A bound can use slack plus an exact filter, or no root at
all (
while d * d <= n). A test needs an exact decision. A band needs exact endpoints on both sides. - Treat $2^{52}$ as the threshold, not $2^{53}$. Float floors and float predicates are both trustworthy only below about $4.5 \times 10^{15}$. Under that, a bare float root is fine as long as the bound on the input is a real constraint of the problem and not just the size of today's test case.
- If you keep the float, make the integers decide. Round, then square and compare against the
original integer, widening to a $\pm 1$ window past $2^{53}$. In scalar Python there is seldom a
reason to bother, because
math.isqrtis exact and faster. The pattern matters for NumPy arrays and for C, which have no integer root built in. - Check the argument as well as the root. A
/, apow(10.0, n)or an implicitint → floatconversion upstream of the root loses precision beforesqrtever runs, and no care taken over the root itself can get it back. See rounding and floating-point arithmetic for the general rules.
Problems11 / 11
- ● 0003 — Largest Prime Factor
- ● 0021 — Amicable Numbers
- ● 0042 — Coded Triangle Numbers
- ● 0045 — Triangular, Pentagonal, and Hexagonal
- ● 0046 — Goldbach's Other Conjecture
- ● 0086 — Cuboid Route
- ● 0098 — Anagramic Squares
- ● 0173 — Hollow Square Laminae I
- ● 0179 — Consecutive Positive Divisors
- ● 0211 — Divisor Square Sum
- ● 0757 — Stealthy Numbers