diff --git a/src/box/tuple_update.c b/src/box/tuple_update.c index 7a203ced82cf298b2c052f87517e28bb283453ff..785a88640ce2ff3fb6fe04abcb0caf86a62da940 100644 --- a/src/box/tuple_update.c +++ b/src/box/tuple_update.c @@ -33,6 +33,7 @@ #include <stdio.h> #include <stdbool.h> +#include <float.h> #include "say.h" #include "error.h" @@ -484,15 +485,15 @@ make_arith_operation(struct op_arith_arg arg1, struct op_arith_arg arg2, err_fieldno, "a positive integer"); return -1; } - if (lowest_type == AT_DOUBLE) { - /* result is DOUBLE */ + float fc = (float)c; + if ((lowest_type == AT_DOUBLE && c != (double) fc) || + (lowest_type == AT_FLOAT && + (c >= FLT_MAX || c <= -FLT_MAX))) { ret->type = AT_DOUBLE; ret->dbl = c; } else { - /* result is FLOAT */ - assert(lowest_type == AT_FLOAT); ret->type = AT_FLOAT; - ret->flt = (float)c; + ret->flt = fc; } } return 0; diff --git a/test/box/update.result b/test/box/update.result index a3f731b55df43f5acc3ad77f1265dfd3d4b7f891..0dbe0f0a1e932f7e8472971802555acf9d69afdd 100644 --- a/test/box/update.result +++ b/test/box/update.result @@ -849,3 +849,68 @@ s:update(1, {{'=', 3, map}}) s:drop() --- ... +-- +-- gh-4701: arith operations should not truncate result when +-- float + float fits double. +-- +msgpackffi = require('msgpackffi') +--- +... +mp_array_1 = 0x91 +--- +... +mp_double = 0xcb +--- +... +mp_float = 0xca +--- +... +flt_max = 3.402823466e+38 +--- +... +uint_max = 18446744073709551615ULL +--- +... +-- Double + double is float if result fits. \ +-- Double + double is double if result does not fit float. \ +-- Double + float is float if result fits. \ +-- Double + float is double if result does not fit float. \ +-- Float + float is float when no overflow. \ +-- Float + float is double when overflow. \ +-- Float + int is float when fits the float range. \ +-- Precision matters too. Double is used when need to avoid \ +-- precision loss. \ +tests = { \ + {{'double', 1}, {'double', 1}, mp_float}, \ + {{'double', flt_max}, {'double', flt_max}, mp_double}, \ + {{'double', 1}, {'float', 1}, mp_float}, \ + {{'double', flt_max}, {'float', flt_max}, mp_double}, \ + {{'float', 1}, {'float', 1}, mp_float}, \ + {{'float', flt_max}, {'float', flt_max}, mp_double}, \ + {{'float', -flt_max}, {'float', -flt_max}, mp_double}, \ + {{'float', 1}, {'int', 1}, mp_float}, \ + {{'float', flt_max}, {'uint64_t', uint_max}, mp_double}, \ + {{'float', 1.0001}, {'double', 1.0000000000001}, mp_double}, \ +} +--- +... +err = nil +--- +... +for i, test in pairs(tests) do \ + local val1 = ffi.cast(test[1][1], test[1][2]) \ + local val2 = ffi.cast(test[2][1], test[2][2]) \ + local t = box.tuple.new({val1}) \ + t = t:update({{'+', 1, val2}}) \ + local m = msgpackffi.encode(t) \ + if m:byte(1) ~= mp_array_1 or m:byte(2) ~= test[3] then \ + err = {i, test, t, m:byte(1), m:byte(2)} \ + break \ + end \ +end +--- +... +err +--- +- null +... diff --git a/test/box/update.test.lua b/test/box/update.test.lua index c455bc1e1e112a02892c6e2c397a7adec3a519ff..724011c38fe6acc2d86cfb00adbd2d6c35893889 100644 --- a/test/box/update.test.lua +++ b/test/box/update.test.lua @@ -269,3 +269,48 @@ t:update({{'=', 3, map}}) s:update(1, {{'=', 3, map}}) s:drop() + +-- +-- gh-4701: arith operations should not truncate result when +-- float + float fits double. +-- +msgpackffi = require('msgpackffi') +mp_array_1 = 0x91 +mp_double = 0xcb +mp_float = 0xca +flt_max = 3.402823466e+38 +uint_max = 18446744073709551615ULL +tests = { \ +-- Double + double is float if result fits. \ + {{'double', 1}, {'double', 1}, mp_float}, \ +-- Double + double is double if result does not fit float. \ + {{'double', flt_max}, {'double', flt_max}, mp_double}, \ +-- Double + float is float if result fits. \ + {{'double', 1}, {'float', 1}, mp_float}, \ +-- Double + float is double if result does not fit float. \ + {{'double', flt_max}, {'float', flt_max}, mp_double}, \ +-- Float + float is float when no overflow. \ + {{'float', 1}, {'float', 1}, mp_float}, \ +-- Float + float is double when overflow. \ + {{'float', flt_max}, {'float', flt_max}, mp_double}, \ + {{'float', -flt_max}, {'float', -flt_max}, mp_double}, \ +-- Float + int is float when fits the float range. \ + {{'float', 1}, {'int', 1}, mp_float}, \ + {{'float', flt_max}, {'uint64_t', uint_max}, mp_double}, \ +-- Precision matters too. Double is used when need to avoid \ +-- precision loss. \ + {{'float', 1.0001}, {'double', 1.0000000000001}, mp_double}, \ +} +err = nil +for i, test in pairs(tests) do \ + local val1 = ffi.cast(test[1][1], test[1][2]) \ + local val2 = ffi.cast(test[2][1], test[2][2]) \ + local t = box.tuple.new({val1}) \ + t = t:update({{'+', 1, val2}}) \ + local m = msgpackffi.encode(t) \ + if m:byte(1) ~= mp_array_1 or m:byte(2) ~= test[3] then \ + err = {i, test, t, m:byte(1), m:byte(2)} \ + break \ + end \ +end +err