[PATCH v3 1/5] decimal: fix ln hang on values between ~ 0.9 and 1.1

Serge Petrenko sergepetrenko at tarantool.org
Tue Jul 2 20:27:48 MSK 2019


Turns out decNumberLn hangs when result is subnormal, according to the
current context settings. To fix this, reset minimal allowed exponent
to a smaller value during the ln operation and round the result afterwards.

Follow-up 6d62c6c19ed418932ead1bba44fcd7cd84c78a19
---
 src/lib/core/decimal.c   | 19 +++++++++++++
 test/unit/decimal.c      |  5 +++-
 test/unit/decimal.result | 58 +++++++++++++++++++++++-----------------
 3 files changed, 56 insertions(+), 26 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 1f9b5838d..c76387d76 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -264,11 +264,30 @@ decimal_log10(decimal_t *res, const decimal_t *lhs)
 decimal_t *
 decimal_ln(decimal_t *res, const decimal_t *lhs)
 {
+	/*
+	 * ln hangs in an infinite loop when result is
+	 * between -10 ^ emin and 10 ^ emin.
+	 * For small x, ln(1 + x) = x. Say, we take the
+	 * smallest allowed value for
+	 * (1 + x) = 1 + 10 ^ -(DECIMAL_MAX_DIGITS - 1).
+	 * For ln to work for this value we need to set emin to
+	 * -DECIMAL_MAX_DIGITS.
+	 */
+	int32_t emin = decimal_context.emin;
+	decimal_context.emin = -DECIMAL_MAX_DIGITS;
+
 	decNumberLn(res, lhs, &decimal_context);
 
+	decimal_context.emin = emin;
 	if (decimal_check_status(&decimal_context) != 0) {
 		return NULL;
 	} else {
+		/*
+		 * The increased EMIN allows for scale up to
+		 * 2 * (DECIMAL_MAX_DIGITS - 1).
+		 * Round back to DECIMAL_MAX_DIGITS - 1.
+		 */
+		decimal_round(res, DECIMAL_MAX_DIGITS - 1);
 		return res;
 	}
 }
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index 7453d13ca..327a06d72 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -121,7 +121,7 @@ test_pack_unpack(void)
 int
 main(void)
 {
-	plan(258);
+	plan(266);
 
 	dectest(314, 271, uint64, uint64_t);
 	dectest(65535, 23456, uint64, uint64_t);
@@ -152,6 +152,9 @@ main(void)
 
 	dectest_op1(log10, 100, 2, 0);
 	dectest_op1(ln, 10, 2.3, 2);
+	dectest_op1(ln, 1.1, 0.1, 1);
+	dectest_op1(ln, 1.0000000000000000000000000000000000001,
+		    0.0000000000000000000000000000000000001, 0);
 	dectest_op1(exp, 2, 7.39, 2);
 	dectest_op1(sqrt, 100, 10, 0);
 
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 051dc7960..7e767dcd2 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..258
+1..266
 ok 1 - decimal(314)
 ok 2 - decimal(271)
 ok 3 - decimal(314) + decimal(271)
@@ -233,29 +233,37 @@ ok 231 - decimal_from_string(10)
 ok 232 - decimal_from_string(2.3)
 ok 233 - decimal_ln(10)
 ok 234 - decimal_compare(2.3)
-ok 235 - decimal_from_string(2)
-ok 236 - decimal_from_string(7.39)
-ok 237 - decimal_exp(2)
-ok 238 - decimal_compare(7.39)
-ok 239 - decimal_from_string(100)
-ok 240 - decimal_from_string(10)
-ok 241 - decimal_sqrt(100)
-ok 242 - decimal_compare(10)
-ok 243 - decimal construction from 2e38 failure
-ok 244 - decimal construction from "1e38" failure
-ok 245 - decimal construction from "100000000000000000000000000000000000000" failure
-ok 246 - decimal construction from LONG_MIN success
-ok 247 - decimal construction from LONG_MAX success
-ok 248 - decimal construction from ULONG_MAX success
-ok 249 - decimal_from_string(9e37)
-ok 250 - decimal_from_string(1e37)
-ok 251 - decimal_add(9e37, 1e37) - overflow
-ok 252 - decimal_from_string(1e19)
-ok 253 - decimal_from_string(1e19)
-ok 254 - decimal_mul(1e19, 1e19) - overflow
-ok 255 - decimal_from_string(1e19)
-ok 256 - decimal_from_string(1e-19)
-ok 257 - decimal_div(1e19, 1e-19) - overflow
+ok 235 - decimal_from_string(1.1)
+ok 236 - decimal_from_string(0.1)
+ok 237 - decimal_ln(1.1)
+ok 238 - decimal_compare(0.1)
+ok 239 - decimal_from_string(1.0000000000000000000000000000000000001)
+ok 240 - decimal_from_string(0.0000000000000000000000000000000000001)
+ok 241 - decimal_ln(1.0000000000000000000000000000000000001)
+ok 242 - decimal_compare(0.0000000000000000000000000000000000001)
+ok 243 - decimal_from_string(2)
+ok 244 - decimal_from_string(7.39)
+ok 245 - decimal_exp(2)
+ok 246 - decimal_compare(7.39)
+ok 247 - decimal_from_string(100)
+ok 248 - decimal_from_string(10)
+ok 249 - decimal_sqrt(100)
+ok 250 - decimal_compare(10)
+ok 251 - decimal construction from 2e38 failure
+ok 252 - decimal construction from "1e38" failure
+ok 253 - decimal construction from "100000000000000000000000000000000000000" failure
+ok 254 - decimal construction from LONG_MIN success
+ok 255 - decimal construction from LONG_MAX success
+ok 256 - decimal construction from ULONG_MAX success
+ok 257 - decimal_from_string(9e37)
+ok 258 - decimal_from_string(1e37)
+ok 259 - decimal_add(9e37, 1e37) - overflow
+ok 260 - decimal_from_string(1e19)
+ok 261 - decimal_from_string(1e19)
+ok 262 - decimal_mul(1e19, 1e19) - overflow
+ok 263 - decimal_from_string(1e19)
+ok 264 - decimal_from_string(1e-19)
+ok 265 - decimal_div(1e19, 1e-19) - overflow
     1..146
     ok 1 - decimal_len(0)
     ok 2 - decimal_len(0) == len(decimal_pack(0)
@@ -403,4 +411,4 @@ ok 257 - decimal_div(1e19, 1e-19) - overflow
     ok 144 - str(decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) == -99999999999999999999999999999999999999
     ok 145 - unpack malformed decimal fails
     ok 146 - decode malformed decimal preserves buffer position
-ok 258 - subtests
+ok 266 - subtests
-- 
2.20.1 (Apple Git-117)




More information about the Tarantool-patches mailing list