An exact fifth root exposes a one-ULP accuracy gap in Swift Numerics
Permanent archive: Zenodo · 10.5281/zenodo.22729051.
Double.root(3125, 5) returns 5.000000000000001 although the exact, representable result is 5.0.
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
5 × 5 × 5 × 5 × 5 = 3125.- Therefore the exact fifth root of 3125 is 5.
- The bit pattern of exact
5.0is0x4014000000000000. - The returned bit pattern is
0x4014000000000001. - The returned value is therefore one representable
Doubleabove the exact answer: a one-ULP gap.
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
RealModuletarget was built and the publicDouble.rootmethod 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.
