Issue Description
The Newton–Raphson loop in root(Number f, unsigned d) (src/libxrpl/basics/Number.cpp#L1593-L1602) stops only when the new value equals the previous one or the one before it:
} while (r != rm1 && r != rm2);
For d >= 3, rounding can make the iterates cycle through three values. Neither condition then holds, and the loop runs forever. power(Number const&, unsigned, unsigned) calls root and hangs on the same inputs.
This happens under every mantissa scale and every rounding mode. Under RoundingMode::ToNearest it affects about 0.45% of random inputs with d from 2 to 6; under the other modes, about 0.04–0.1%.
No root2 hang was found in 3.2 million random inputs across all scales and rounding modes. Only tests call root(f, d) and power(f, n, d); ledger code uses root2 and power(f, n).
Steps to Reproduce
Add to src/tests/libxrpl/basics/Number.cpp:
TEST(NumberTest, root_cycle)
{
{
NumberMantissaScaleGuard const sg(MantissaRange::MantissaScale::Large330);
(void)root(Number{298}, 4); // never returns
(void)power(Number{298}, 1, 4); // never returns
}
{
NumberMantissaScaleGuard const sg(MantissaRange::MantissaScale::Small);
(void)root(Number{147}, 3); // never returns
}
}
More inputs with the default rounding mode:
LargeLegacy, Large320 and Large330: root(Number{298}, 4), root(Number{380}, 4) and root(Number{438}, 4).
Small: root(Number{147}, 3), root(Number{573}, 4) and root(Number{612}, 4).
Expected Result
root returns a value (about 4.154837722659222 for root(298, 4)) or throws.
Actual Result
The call does not return. These are the iterates of r for root(Number{298}, 4) under Large330, where f is scaled to 0.0298:
1 0.422698722585301542
2 0.415666404778574261
3 0.4154838925963071552
4 0.4154837722659744575
5 0.415483772265922183
6 0.4154837722659221832
7 0.4154837722659221835
8 0.415483772265922183
9 0.4154837722659221832
10 0.4154837722659221835
The iterates under Small for root(Number{147}, 3), where f is scaled to 0.147:
4 0.5277632087904077
5 0.5277632087904073
6 0.527763208790408
7 0.5277632087904077
8 0.5277632087904073
9 0.527763208790408
Environment
Number.cpp and Number.h at develop 1d7669f528ce83113c1ef2d1c8ea4675ce19157d. The loop is identical in 3.4.0.
- Ubuntu 24.04.4 LTS, g++ 13.3.0,
-std=c++23 -O2.
Supporting Files
None.
Issue Description
The Newton–Raphson loop in
root(Number f, unsigned d)(src/libxrpl/basics/Number.cpp#L1593-L1602) stops only when the new value equals the previous one or the one before it:} while (r != rm1 && r != rm2);For
d >= 3, rounding can make the iterates cycle through three values. Neither condition then holds, and the loop runs forever.power(Number const&, unsigned, unsigned)callsrootand hangs on the same inputs.This happens under every mantissa scale and every rounding mode. Under
RoundingMode::ToNearestit affects about 0.45% of random inputs withdfrom 2 to 6; under the other modes, about 0.04–0.1%.No
root2hang was found in 3.2 million random inputs across all scales and rounding modes. Only tests callroot(f, d)andpower(f, n, d); ledger code usesroot2andpower(f, n).Steps to Reproduce
Add to
src/tests/libxrpl/basics/Number.cpp:More inputs with the default rounding mode:
LargeLegacy,Large320andLarge330:root(Number{298}, 4),root(Number{380}, 4)androot(Number{438}, 4).Small:root(Number{147}, 3),root(Number{573}, 4)androot(Number{612}, 4).Expected Result
rootreturns a value (about 4.154837722659222 forroot(298, 4)) or throws.Actual Result
The call does not return. These are the iterates of
rforroot(Number{298}, 4)underLarge330, wherefis scaled to0.0298:The iterates under
Smallforroot(Number{147}, 3), wherefis scaled to0.147:Environment
Number.cppandNumber.hat develop1d7669f528ce83113c1ef2d1c8ea4675ce19157d. The loop is identical in 3.4.0.-std=c++23 -O2.Supporting Files
None.