Make reading of Decimal more compatible with other DBMS

This commit is contained in:
Alexey Milovidov 2020-06-20 17:43:01 +03:00
parent 7b2b726c17
commit 408fc241d6
5 changed files with 87 additions and 18 deletions

View File

@ -134,3 +134,14 @@ inline __int128 exp10_i128(int x)
} }
} }
/// intExp10 returning the type T.
template <typename T>
inline T intExp10OfSize(int x)
{
if constexpr (sizeof(T) <= 8)
return intExp10(x);
else
return common::exp10_i128(x);
}

View File

@ -31,8 +31,13 @@ template <> struct FunctionUnaryArithmeticMonotonicity<NameIntExp10>
static bool has() { return true; } static bool has() { return true; }
static IFunction::Monotonicity get(const Field & left, const Field & right) static IFunction::Monotonicity get(const Field & left, const Field & right)
{ {
Float64 left_float = left.isNull() ? -std::numeric_limits<Float64>::infinity() : applyVisitor(FieldVisitorConvertToNumber<Float64>(), left); Float64 left_float = left.isNull()
Float64 right_float = right.isNull() ? std::numeric_limits<Float64>::infinity() : applyVisitor(FieldVisitorConvertToNumber<Float64>(), right); ? -std::numeric_limits<Float64>::infinity()
: applyVisitor(FieldVisitorConvertToNumber<Float64>(), left);
Float64 right_float = right.isNull()
? std::numeric_limits<Float64>::infinity()
: applyVisitor(FieldVisitorConvertToNumber<Float64>(), right);
if (left_float < 0 || right_float > 19) if (left_float < 0 || right_float > 19)
return {}; return {};

View File

@ -1,4 +1,5 @@
#include <IO/ReadHelpers.h> #include <IO/ReadHelpers.h>
#include <Common/intExp.h>
namespace DB namespace DB
@ -79,23 +80,35 @@ inline bool readDigits(ReadBuffer & buf, T & x, uint32_t & digits, int32_t & exp
++places; // num zeroes before + current digit ++places; // num zeroes before + current digit
if (digits + places > max_digits) if (digits + places > max_digits)
{
if (after_point)
{
/// Simply cut excessive digits.
break;
}
else
{ {
if constexpr (_throw_on_error) if constexpr (_throw_on_error)
throw Exception("Too many digits (" + std::to_string(digits + places) + " > " + std::to_string(max_digits) throw Exception("Too many digits (" + std::to_string(digits + places) + " > " + std::to_string(max_digits)
+ ") in decimal value", ErrorCodes::ARGUMENT_OUT_OF_BOUND); + ") in decimal value", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
return false; return false;
} }
}
else
{
digits += places; digits += places;
if (after_point) if (after_point)
exponent -= places; exponent -= places;
// TODO: accurate shift10 for big integers // TODO: accurate shift10 for big integers
for (; places; --places) x *= intExp10OfSize<T>(places);
x *= 10; places = 0;
x += (byte - '0'); x += (byte - '0');
break; break;
} }
}
case 'e': [[fallthrough]]; case 'e': [[fallthrough]];
case 'E': case 'E':
{ {
@ -144,10 +157,12 @@ inline void readDecimalText(ReadBuffer & buf, T & x, uint32_t precision, uint32_
digits, x, exponent, scale, precision), ErrorCodes::ARGUMENT_OUT_OF_BOUND); digits, x, exponent, scale, precision), ErrorCodes::ARGUMENT_OUT_OF_BOUND);
if (static_cast<int32_t>(scale) + exponent < 0) if (static_cast<int32_t>(scale) + exponent < 0)
throw Exception(fmt::format( {
"Decimal value has too large number of digits after point: {} digits were read: {}e{}." /// Too many digits after point. Just cut off excessive digits.
" Expected to read decimal with scale {} and precision {}", x.value /= intExp10OfSize<T>(-exponent - scale);
digits, x, exponent, scale, precision), ErrorCodes::ARGUMENT_OUT_OF_BOUND); scale = 0;
return;
}
scale += exponent; scale += exponent;
} }

View File

@ -0,0 +1,14 @@
1.10000
1.12345
1.12345
1.12345
1.12345
1.12345
12345.10000
123456789123.10000
1234567891234.10000
1234567891234.12345
1.12345
1.12345
1.12344
1.12345

View File

@ -0,0 +1,24 @@
SELECT CAST('1.1' AS Decimal(10, 5));
SELECT CAST('1.12345' AS Decimal(10, 5));
SELECT CAST('1.123451' AS Decimal(10, 5));
SELECT CAST('1.1234511111' AS Decimal(10, 5));
SELECT CAST('1.12345111111' AS Decimal(10, 5));
SELECT CAST('1.12345111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' AS Decimal(10, 5));
SELECT CAST('12345.1' AS Decimal(10, 5));
-- Actually our decimal can contain more than 10 digits for free.
SELECT CAST('123456789123.1' AS Decimal(10, 5));
SELECT CAST('1234567891234.1' AS Decimal(10, 5));
SELECT CAST('1234567891234.12345111' AS Decimal(10, 5));
-- But it's just Decimal64, so there is the limit.
SELECT CAST('12345678912345.1' AS Decimal(10, 5)); -- { serverError 69 }
-- The rounding may work in unexpected way: this is just integer rounding.
-- We can improve it but here is the current behaviour:
SELECT CAST('1.123455' AS Decimal(10, 5));
SELECT CAST('1.123456' AS Decimal(10, 5));
SELECT CAST('1.123445' AS Decimal(10, 5)); -- Check if suddenly banker's rounding will be implemented.
CREATE TEMPORARY TABLE test (x Decimal(10, 5));
INSERT INTO test VALUES (1.12345111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111);
SELECT * FROM test;