fix decimal to int, int to decimal convertion [issue-3177]

This commit is contained in:
chertus 2018-09-25 19:03:50 +03:00
parent 002331be1b
commit 96ceca6c05
4 changed files with 210 additions and 39 deletions

View File

@ -1,5 +1,5 @@
#pragma once
#include <common/likely.h>
#include <common/arithmeticOverflow.h>
#include <Common/typeid_cast.h>
#include <Columns/ColumnDecimal.h>
#include <DataTypes/IDataType.h>
@ -12,9 +12,9 @@ namespace DB
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
extern const int ARGUMENT_OUT_OF_BOUND;
extern const int CANNOT_CONVERT_TYPE;
extern const int DECIMAL_OVERFLOW;
}
class Context;
@ -39,9 +39,7 @@ DataTypePtr createDecimal(UInt64 precision, UInt64 scale);
/// Int128 38
/// Operation between two decimals leads to Decimal(P, S), where
/// P is one of (9, 18, 38); equals to the maximum precision for the biggest underlying type of operands.
/// S is maximum scale of operands.
///
/// NOTE: It's possible to set scale as a template parameter then most of functions become static.
/// S is maximum scale of operands. The allowed valuas are [0, precision]
template <typename T>
class DataTypeDecimal final : public DataTypeWithSimpleSerialization
{
@ -156,7 +154,7 @@ public:
private:
const UInt32 precision;
const UInt32 scale; /// TODO: should we support scales out of [0, precision]?
const UInt32 scale;
};
@ -221,49 +219,86 @@ template <> constexpr bool IsDataTypeDecimal<DataTypeDecimal<Decimal32>> = true;
template <> constexpr bool IsDataTypeDecimal<DataTypeDecimal<Decimal64>> = true;
template <> constexpr bool IsDataTypeDecimal<DataTypeDecimal<Decimal128>> = true;
template <typename DataType> constexpr bool IsDataTypeDecimalOrNumber = IsDataTypeDecimal<DataType> || IsDataTypeNumber<DataType>;
template <typename FromDataType, typename ToDataType>
inline std::enable_if_t<IsDataTypeDecimal<FromDataType> && IsDataTypeDecimal<ToDataType>, typename ToDataType::FieldType>
convertDecimals(const typename FromDataType::FieldType & value, UInt32 scale_from, UInt32 scale_to)
{
ToDataType type_to(ToDataType::maxPrecision(), scale_to);
FromDataType type_from(FromDataType::maxPrecision(), scale_from);
using FromFieldType = typename FromDataType::FieldType;
using ToFieldType = typename ToDataType::FieldType;
using MaxFieldType = std::conditional_t<(sizeof(FromFieldType) > sizeof(ToFieldType)), FromFieldType, ToFieldType>;
using MaxNativeType = typename MaxFieldType::NativeType;
if (scale_from > scale_to)
MaxNativeType converted_value;
if (scale_to > scale_from)
{
typename FromDataType::FieldType factor = type_from.scaleFactorFor(type_to, false);
return value / factor;
converted_value = DataTypeDecimal<MaxFieldType>::getScaleMultiplier(scale_to - scale_from);
if (common::mulOverflow(static_cast<MaxNativeType>(value), converted_value, converted_value))
throw Exception("Decimal convert overflow", ErrorCodes::DECIMAL_OVERFLOW);
}
else
converted_value = value / DataTypeDecimal<MaxFieldType>::getScaleMultiplier(scale_from - scale_to);
if constexpr (sizeof(FromFieldType) > sizeof(ToFieldType))
{
if (converted_value < std::numeric_limits<typename ToFieldType::NativeType>::min() ||
converted_value > std::numeric_limits<typename ToFieldType::NativeType>::max())
throw Exception("Decimal convert overflow", ErrorCodes::DECIMAL_OVERFLOW);
}
return converted_value;
}
template <typename FromDataType, typename ToDataType>
inline std::enable_if_t<IsDataTypeDecimal<FromDataType> && IsDataTypeNumber<ToDataType>, typename ToDataType::FieldType>
convertFromDecimal(const typename FromDataType::FieldType & value, UInt32 scale)
{
using FromFieldType = typename FromDataType::FieldType;
using ToFieldType = typename ToDataType::FieldType;
if constexpr (std::is_floating_point_v<ToFieldType>)
return static_cast<ToFieldType>(value) / FromDataType::getScaleMultiplier(scale);
else
{
FromFieldType converted_value = convertDecimals<FromDataType, FromDataType>(value, scale, 0);
if constexpr (sizeof(FromFieldType) > sizeof(ToFieldType) || !std::numeric_limits<ToFieldType>::is_signed)
{
if constexpr (std::numeric_limits<ToFieldType>::is_signed)
{
if (converted_value < std::numeric_limits<ToFieldType>::min() ||
converted_value > std::numeric_limits<ToFieldType>::max())
throw Exception("Decimal convert overflow", ErrorCodes::DECIMAL_OVERFLOW);
}
else
{
typename ToDataType::FieldType factor = type_to.scaleFactorFor(type_from, false);
return value * factor;
using CastIntType = std::conditional_t<std::is_same_v<ToFieldType, UInt64>, Int128, Int64>;
if (converted_value < 0 ||
converted_value > static_cast<CastIntType>(std::numeric_limits<ToFieldType>::max()))
throw Exception("Decimal convert overflow", ErrorCodes::DECIMAL_OVERFLOW);
}
}
return converted_value;
}
}
template <typename FromDataType, typename ToDataType>
inline std::enable_if_t<IsDataTypeDecimal<FromDataType> && !IsDataTypeDecimal<ToDataType>, typename ToDataType::FieldType>
convertFromDecimal(const typename FromDataType::FieldType & value, UInt32 scale [[maybe_unused]])
inline std::enable_if_t<IsDataTypeNumber<FromDataType> && IsDataTypeDecimal<ToDataType>, typename ToDataType::FieldType>
convertToDecimal(const typename FromDataType::FieldType & value, UInt32 scale)
{
if (scale > FromDataType::maxPrecision())
throw Exception("Wrong decimal scale", ErrorCodes::LOGICAL_ERROR);
using FromFieldType = typename FromDataType::FieldType;
if constexpr (!std::is_same_v<ToDataType, DataTypeNumber<typename ToDataType::FieldType>>)
throw Exception("Illegal convertion from decimal", ErrorCodes::CANNOT_CONVERT_TYPE);
else
return static_cast<typename ToDataType::FieldType>(value) / FromDataType::getScaleMultiplier(scale);
}
template <typename FromDataType, typename ToDataType>
inline std::enable_if_t<!IsDataTypeDecimal<FromDataType> && IsDataTypeDecimal<ToDataType>, typename ToDataType::FieldType>
convertToDecimal(const typename FromDataType::FieldType & value, UInt32 scale [[maybe_unused]])
{
if (scale > ToDataType::maxPrecision())
throw Exception("Wrong decimal scale", ErrorCodes::LOGICAL_ERROR);
if constexpr (!std::is_same_v<FromDataType, DataTypeNumber<typename FromDataType::FieldType>>)
throw Exception("Illegal convertion to decimal", ErrorCodes::CANNOT_CONVERT_TYPE);
else
if constexpr (std::is_floating_point_v<FromFieldType>)
return value * ToDataType::getScaleMultiplier(scale);
else
{
if constexpr (std::is_same_v<FromFieldType, UInt64>)
if (value > static_cast<UInt64>(std::numeric_limits<Int64>::max()))
return convertDecimals<DataTypeDecimal<Decimal128>, ToDataType>(value, 0, scale);
return convertDecimals<DataTypeDecimal<Decimal64>, ToDataType>(value, 0, scale);
}
}
}

View File

@ -107,6 +107,13 @@ struct ConvertImpl
using ColVecFrom = std::conditional_t<IsDecimalNumber<FromFieldType>, ColumnDecimal<FromFieldType>, ColumnVector<FromFieldType>>;
using ColVecTo = std::conditional_t<IsDecimalNumber<ToFieldType>, ColumnDecimal<ToFieldType>, ColumnVector<ToFieldType>>;
if constexpr (IsDataTypeDecimal<FromDataType> || IsDataTypeDecimal<ToDataType>)
{
if constexpr (!IsDataTypeDecimalOrNumber<FromDataType> || !IsDataTypeDecimalOrNumber<ToDataType>)
throw Exception("Illegal column " + named_from.column->getName() + " of first argument of function " + Name::name,
ErrorCodes::ILLEGAL_COLUMN);
}
if (const ColVecFrom * col_from = checkAndGetColumn<ColVecFrom>(named_from.column.get()))
{
typename ColVecTo::MutablePtr col_to = nullptr;
@ -124,13 +131,16 @@ struct ConvertImpl
vec_to.resize(size);
for (size_t i = 0; i < size; ++i)
{
if constexpr (IsDataTypeDecimal<FromDataType> || IsDataTypeDecimal<ToDataType>)
{
if constexpr (IsDataTypeDecimal<FromDataType> && IsDataTypeDecimal<ToDataType>)
vec_to[i] = convertDecimals<FromDataType, ToDataType>(vec_from[i], vec_from.getScale(), vec_to.getScale());
else if constexpr (IsDataTypeDecimal<FromDataType>)
else if constexpr (IsDataTypeDecimal<FromDataType> && IsDataTypeNumber<ToDataType>)
vec_to[i] = convertFromDecimal<FromDataType, ToDataType>(vec_from[i], vec_from.getScale());
else if constexpr (IsDataTypeDecimal<ToDataType>)
else if constexpr (IsDataTypeNumber<FromDataType> && IsDataTypeDecimal<ToDataType>)
vec_to[i] = convertToDecimal<FromDataType, ToDataType>(vec_from[i], vec_to.getScale());
}
else
vec_to[i] = static_cast<ToFieldType>(vec_from[i]);
}

View File

@ -129,3 +129,39 @@
12345678901234567890123456789012345678
0.123456789 0.123456789123456789
0.12345678901234567890123456789012345678
1234567890.0000000000000000000000000000 1234567890.00000000000000000000000000000 1234567890.00000000000000000000000000000
1234567890.00000000 1234567890.000000000 1234567890.000000000
12345678.0 12345678.00 12345678.00
9223372036854775807.000000 9223372036854775807 -9223372036854775807
9223372036854775800 9223372036854775800 -9223372036854775800
92233720368547758.00 92233720368547758 -92233720368547758
2147483647.0000000000 2147483647 -2147483647
2147483647.00 2147483647 -2147483647
92233720368547757.99 92233720368547757 -92233720368547757
2147483640.99 2147483640 -2147483640
-0.90000000 0
-0.90000000 0
-0.90000000 0
-0.8000 0
-0.8000 0
-0.8000 0
-0.70 0
-0.70 0
-0.70 0
-0.600000 0
-0.600000 0
-0.600000 0
18446744073709551615 18446744073709551615
18446744073709551615.00000000 18446744073709551615
4294967295 4294967295
4294967295.0000000000 4294967295
4294967295 4294967295
4294967295.0000 4294967295
65535 65535
65535.0000000000 65535
65535 65535
65535.0000 65535
2147483647 2147483647
-2147483647 -2147483647
2147483647 2147483647
9223372036854775807 9223372036854775807

View File

@ -150,4 +150,94 @@ SELECT CAST('0.123456789', 'Decimal(9,8)'); -- { serverError 69 }
SELECT CAST('0.123456789123456789', 'Decimal(18,17)'); -- { serverError 69 }
SELECT CAST('0.12345678901234567890123456789012345678', 'Decimal(38,37)'); -- { serverError 69 }
SELECT toDecimal128('1234567890', 28) AS x, toDecimal128(x, 29), toDecimal128(toDecimal128('1234567890', 28), 29);
SELECT toDecimal128(toDecimal128('1234567890', 28), 30); -- { serverError 407 }
SELECT toDecimal64('1234567890', 8) AS x, toDecimal64(x, 9), toDecimal64(toDecimal64('1234567890', 8), 9);
SELECT toDecimal64(toDecimal64('1234567890', 8), 10); -- { serverError 407 }
SELECT toDecimal32('12345678', 1) AS x, toDecimal32(x, 2), toDecimal32(toDecimal32('12345678', 1), 2);
SELECT toDecimal32(toDecimal32('12345678', 1), 3); -- { serverError 407 }
SELECT toDecimal64(toDecimal64('92233720368547758.1', 1), 2); -- { serverError 407 }
SELECT toDecimal64(toDecimal64('-92233720368547758.1', 1), 2); -- { serverError 407 }
SELECT toDecimal128('9223372036854775807', 6) AS x, toInt64(x), toInt64(-x);
SELECT toDecimal128('9223372036854775809', 6) AS x, toInt64(x); -- { serverError 407 }
SELECT toDecimal128('9223372036854775809', 6) AS x, toInt64(-x); -- { serverError 407 }
SELECT toDecimal64('922337203685477580', 0) * 10 AS x, toInt64(x), toInt64(-x);
SELECT toDecimal64(toDecimal64('92233720368547758.0', 1), 2) AS x, toInt64(x), toInt64(-x);
SELECT toDecimal128('2147483647', 10) AS x, toInt32(x), toInt32(-x);
SELECT toDecimal128('2147483649', 10) AS x, toInt32(x), toInt32(-x); -- { serverError 407 }
SELECT toDecimal64('2147483647', 2) AS x, toInt32(x), toInt32(-x);
SELECT toDecimal64('2147483649', 2) AS x, toInt32(x), toInt32(-x); -- { serverError 407 }
SELECT toDecimal128('92233720368547757.99', 2) AS x, toInt64(x), toInt64(-x);
SELECT toDecimal64('2147483640.99', 2) AS x, toInt32(x), toInt32(-x);
SELECT toDecimal128('-0.9', 8) AS x, toUInt64(x);
SELECT toDecimal64('-0.9', 8) AS x, toUInt64(x);
SELECT toDecimal32('-0.9', 8) AS x, toUInt64(x);
SELECT toDecimal128('-0.8', 4) AS x, toUInt32(x);
SELECT toDecimal64('-0.8', 4) AS x, toUInt32(x);
SELECT toDecimal32('-0.8', 4) AS x, toUInt32(x);
SELECT toDecimal128('-0.7', 2) AS x, toUInt16(x);
SELECT toDecimal64('-0.7', 2) AS x, toUInt16(x);
SELECT toDecimal32('-0.7', 2) AS x, toUInt16(x);
SELECT toDecimal128('-0.6', 6) AS x, toUInt8(x);
SELECT toDecimal64('-0.6', 6) AS x, toUInt8(x);
SELECT toDecimal32('-0.6', 6) AS x, toUInt8(x);
SELECT toDecimal128('-1', 7) AS x, toUInt64(x); -- { serverError 407 }
SELECT toDecimal128('-1', 7) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal128('-1', 7) AS x, toUInt16(x); -- { serverError 407 }
SELECT toDecimal128('-1', 7) AS x, toUInt8(x); -- { serverError 407 }
SELECT toDecimal64('-1', 5) AS x, toUInt64(x); -- { serverError 407 }
SELECT toDecimal64('-1', 5) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal64('-1', 5) AS x, toUInt16(x); -- { serverError 407 }
SELECT toDecimal64('-1', 5) AS x, toUInt8(x); -- { serverError 407 }
SELECT toDecimal32('-1', 3) AS x, toUInt64(x); -- { serverError 407 }
SELECT toDecimal32('-1', 3) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal32('-1', 3) AS x, toUInt16(x); -- { serverError 407 }
SELECT toDecimal32('-1', 3) AS x, toUInt8(x); -- { serverError 407 }
SELECT toDecimal128('18446744073709551615', 0) AS x, toUInt64(x);
SELECT toDecimal128('18446744073709551616', 0) AS x, toUInt64(x); -- { serverError 407 }
SELECT toDecimal128('18446744073709551615', 8) AS x, toUInt64(x);
SELECT toDecimal128('18446744073709551616', 8) AS x, toUInt64(x); -- { serverError 407 }
SELECT toDecimal128('4294967295', 0) AS x, toUInt32(x);
SELECT toDecimal128('4294967296', 0) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal128('4294967295', 10) AS x, toUInt32(x);
SELECT toDecimal128('4294967296', 10) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal64('4294967295', 0) AS x, toUInt32(x);
SELECT toDecimal64('4294967296', 0) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal64('4294967295', 4) AS x, toUInt32(x);
SELECT toDecimal64('4294967296', 4) AS x, toUInt32(x); -- { serverError 407 }
SELECT toDecimal128('65535', 0) AS x, toUInt16(x);
SELECT toDecimal128('65536', 0) AS x, toUInt16(x); -- { serverError 407 }
SELECT toDecimal128('65535', 10) AS x, toUInt16(x);
SELECT toDecimal128('65536', 10) AS x, toUInt16(x); -- { serverError 407 }
SELECT toDecimal64('65535', 0) AS x, toUInt16(x);
SELECT toDecimal64('65536', 0) AS x, toUInt16(x); -- { serverError 407 }
SELECT toDecimal64('65535', 4) AS x, toUInt16(x);
SELECT toDecimal64('65536', 4) AS x, toUInt16(x); -- { serverError 407 }
SELECT toInt64('2147483647') AS x, toDecimal32(x, 0);
SELECT toInt64('-2147483647') AS x, toDecimal32(x, 0);
SELECT toUInt64('2147483647') AS x, toDecimal32(x, 0);
SELECT toInt64('2147483649') AS x, toDecimal32(x, 0); -- { serverError 407 }
SELECT toInt64('-2147483649') AS x, toDecimal32(x, 0); -- { serverError 407 }
SELECT toUInt64('2147483649') AS x, toDecimal32(x, 0); -- { serverError 407 }
SELECT toUInt64('9223372036854775807') AS x, toDecimal64(x, 0);
SELECT toUInt64('9223372036854775809') AS x, toDecimal64(x, 0); -- { serverError 407 }
DROP TABLE IF EXISTS test.decimal;