[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