Fix ApproxRoot Infinite Looping (#7140)

Added a maximum number of iterations (100) to ApproxRoot
(and ApproxSqrt) to serve as a hard limit on the number of
iterations that the algorithm performs. If the answer converges
before the maximum iterations, it returns immediately. Otherwise,
if the answer never converges enough to satisfy the primary loop
condition, the looping stops after a hundred iterations.

Closes: #7038

Co-authored-by: Alessio Treglia <quadrispro@ubuntu.com>
This commit is contained in:
Miguel Dingli
2020-08-29 11:13:36 +01:00
committed by GitHub
co-authored by Alessio Treglia
parent d02cd16219
commit 018915b1a8
3 changed files with 22 additions and 9 deletions
+7 -2
View File
@@ -18,13 +18,16 @@ type Dec struct {
i *big.Int
}
// number of decimal places
const (
// number of decimal places
Precision = 18
// bytes required to represent the above precision
// Ceiling[Log2[999 999 999 999 999 999]]
DecimalPrecisionBits = 60
// max number of iterations in ApproxRoot function
maxApproxRootIterations = 100
)
var (
@@ -339,6 +342,8 @@ func (d Dec) QuoInt64(i int64) Dec {
// using Newton's method (where n is positive). The algorithm starts with some guess and
// computes the sequence of improved guesses until an answer converges to an
// approximate answer. It returns `|d|.ApproxRoot() * -1` if input is negative.
// A maximum number of 100 iterations is used a backup boundary condition for
// cases where the answer never converges enough to satisfy the main condition.
func (d Dec) ApproxRoot(root uint64) (guess Dec, err error) {
defer func() {
if r := recover(); r != nil {
@@ -366,7 +371,7 @@ func (d Dec) ApproxRoot(root uint64) (guess Dec, err error) {
rootInt := NewIntFromUint64(root)
guess, delta := OneDec(), OneDec()
for delta.Abs().GT(SmallestDec()) {
for iter := 0; delta.Abs().GT(SmallestDec()) && iter < maxApproxRootIterations; iter++ {
prev := guess.Power(root - 1)
if prev.IsZero() {
prev = SmallestDec()
+14 -7
View File
@@ -406,15 +406,22 @@ func TestApproxRoot(t *testing.T) {
root uint64
expected Dec
}{
{OneDec(), 10, OneDec()}, // 1.0 ^ (0.1) => 1.0
{NewDecWithPrec(25, 2), 2, NewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5
{NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)}, // 0.04 => 0.2
{NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))}, // 27 ^ (1/3) => 3
{NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))}, // -81 ^ (0.25) => -3
{NewDecFromInt(NewInt(2)), 2, NewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049
{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153904")},
{OneDec(), 10, OneDec()}, // 1.0 ^ (0.1) => 1.0
{NewDecWithPrec(25, 2), 2, NewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5
{NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2
{NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))}, // 27 ^ (1/3) => 3
{NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))}, // -81 ^ (0.25) => -3
{NewDecFromInt(NewInt(2)), 2, NewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049
{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016
{SmallestDec(), 2, NewDecWithPrec(1, 9)}, // 1e-18 ^ (0.5) => 1e-9
{SmallestDec(), 3, MustNewDecFromStr("0.000000999999999997")}, // 1e-18 ^ (1/3) => 1e-6
{NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031900")}, // 1e-8 ^ (1/3) ≈ 0.00215443469
}
// In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24
// (i.e. 24, 29, 34, ... give the same result) and never converges enough. The maximum number of
// iterations (100) causes the result at iteration 100 to be returned, regardless of convergence.
for i, tc := range testCases {
res, err := tc.input.ApproxRoot(tc.root)
require.NoError(t, err)