← Research index
INDEPENDENT NUMERICAL AUDIT3 September 2026

An exact fifth root exposes a one-ULP accuracy gap in Swift Numerics

Double.root(3125, 5) returns 5.000000000000001 although the exact, representable result is 5.0.

Xamit Kadirbekov
Xamit KadirbekovIndependent verification · GERO Research
Numerical auditSwiftFloating point
STATUS · VERIFIED 1-ULP COUNTEREXAMPLEThe public implementation was built and executed at the cited commit. This is an accuracy finding, not a security finding.
REVIEWED SOURCE · APPLE SWIFT NUMERICS Exact implementation and commit ↗

Finding

At source commit 899af71c0256d0ad181e3b7eb3453c1065d928a5, the public call Double.root(3125.0, 5) returns 5.000000000000001. The mathematically exact result is 5.0, which is exactly representable as a binary Double.

Minimal reproducer

import RealModule

let result = Double.root(3125.0, 5)
print(result)
print(result == 5.0)
print(String(result.bitPattern, radix: 16))

Observed output:

5.000000000000001
false
4014000000000001

Exact certificate

  1. 5 × 5 × 5 × 5 × 5 = 3125.
  2. Therefore the exact fifth root of 3125 is 5.
  3. The bit pattern of exact 5.0 is 0x4014000000000000.
  4. The returned bit pattern is 0x4014000000000001.
  5. The returned value is therefore one representable Double above the exact answer: a one-ULP gap.
⁵√3125 = 5implementation → 5 + 1 ULP

Why it happens

The implementation reduces an integer root to a general power:

libm_pow(x.magnitude, 1 / Double(n))

The binary Double format cannot represent 1/5 exactly. That rounded exponent is then passed to pow. The source already contains a TODO noting that the implementation is “not quite correct” because either n or 1/n may not be representable as Double. This audit supplies a small, concrete regression case for that acknowledged limitation.

Reproduction record

  • Repository: apple/swift-numerics.
  • Commit: 899af71c0256d0ad181e3b7eb3453c1065d928a5.
  • Toolchain: Apple Swift 6.1.2.
  • Target: arm64 Apple macOS.
  • The RealModule target was built and the public Double.root method was called directly.

Question for upstream

Should root(x, n) guarantee a correctly rounded result when the exact root is representable, or is a small approximation error part of the intended contract? In either case, root(3125, 5) is a useful regression and documentation test because it makes the current accuracy boundary explicit.