diff --git a/docs/en/sql-reference/functions/arithmetic-functions.md b/docs/en/sql-reference/functions/arithmetic-functions.md index ece50591ef9..56f3a88b28b 100644 --- a/docs/en/sql-reference/functions/arithmetic-functions.md +++ b/docs/en/sql-reference/functions/arithmetic-functions.md @@ -161,3 +161,140 @@ Result: │ -1 │ └─────────────┘ ``` + +## multiplyDecimal(a, b[, result_scale]) + +Performs multiplication on two decimals. Result value will be of type [Decimal256](../../sql-reference/data-types/decimal.md). +Result scale can be explicitly specified by `result_scale` argument (const Integer in range `[0, 76]`). If not specified, the result scale is the max scale of given arguments. + +:::note +These functions work significantly slower than usual `multiply`. +In case you don't really need controlled precision and/or need fast computation, consider using [multiply](#multiply) +::: + +**Syntax** + +```sql +multiplyDecimal(a, b[, result_scale]) +``` + +**Arguments** + +- `a` — First value: [Decimal](../../sql-reference/data-types/decimal.md). +- `b` — Second value: [Decimal](../../sql-reference/data-types/decimal.md). +- `result_scale` — Scale of result: [Int/UInt](../../sql-reference/data-types/int-uint.md). + +**Returned value** + +- The result of multiplication with given scale. + +Type: [Decimal256](../../sql-reference/data-types/decimal.md). + +**Example** + +```text +┌─multiplyDecimal(toDecimal256(-12, 0), toDecimal32(-2.1, 1), 1)─┐ +│ 25.2 │ +└────────────────────────────────────────────────────────────────┘ +``` + +**Difference from regular multiplication:** +```sql +SELECT toDecimal64(-12.647, 3) * toDecimal32(2.1239, 4); +SELECT toDecimal64(-12.647, 3) as a, toDecimal32(2.1239, 4) as b, multiplyDecimal(a, b); +``` + +```text +┌─multiply(toDecimal64(-12.647, 3), toDecimal32(2.1239, 4))─┐ +│ -26.8609633 │ +└───────────────────────────────────────────────────────────┘ +┌─multiplyDecimal(toDecimal64(-12.647, 3), toDecimal32(2.1239, 4))─┐ +│ -26.8609 │ +└──────────────────────────────────────────────────────────────────┘ +``` + +```sql +SELECT + toDecimal64(-12.647987876, 9) AS a, + toDecimal64(123.967645643, 9) AS b, + multiplyDecimal(a, b); + +SELECT + toDecimal64(-12.647987876, 9) AS a, + toDecimal64(123.967645643, 9) AS b, + a * b; +``` + +```text +┌─────────────a─┬─────────────b─┬─multiplyDecimal(toDecimal64(-12.647987876, 9), toDecimal64(123.967645643, 9))─┐ +│ -12.647987876 │ 123.967645643 │ -1567.941279108 │ +└───────────────┴───────────────┴───────────────────────────────────────────────────────────────────────────────┘ + +Received exception from server (version 22.11.1): +Code: 407. DB::Exception: Received from localhost:9000. DB::Exception: Decimal math overflow: While processing toDecimal64(-12.647987876, 9) AS a, toDecimal64(123.967645643, 9) AS b, a * b. (DECIMAL_OVERFLOW) +``` + +## divideDecimal(a, b[, result_scale]) + +Performs division on two decimals. Result value will be of type [Decimal256](../../sql-reference/data-types/decimal.md). +Result scale can be explicitly specified by `result_scale` argument (const Integer in range `[0, 76]`). If not specified, the result scale is the max scale of given arguments. + +:::note +These function work significantly slower than usual `divide`. +In case you don't really need controlled precision and/or need fast computation, consider using [divide](#divide). +::: + +**Syntax** + +```sql +divideDecimal(a, b[, result_scale]) +``` + +**Arguments** + +- `a` — First value: [Decimal](../../sql-reference/data-types/decimal.md). +- `b` — Second value: [Decimal](../../sql-reference/data-types/decimal.md). +- `result_scale` — Scale of result: [Int/UInt](../../sql-reference/data-types/int-uint.md). + +**Returned value** + +- The result of division with given scale. + +Type: [Decimal256](../../sql-reference/data-types/decimal.md). + +**Example** + +```text +┌─divideDecimal(toDecimal256(-12, 0), toDecimal32(2.1, 1), 10)─┐ +│ -5.7142857142 │ +└──────────────────────────────────────────────────────────────┘ +``` + +**Difference from regular division:** +```sql +SELECT toDecimal64(-12, 1) / toDecimal32(2.1, 1); +SELECT toDecimal64(-12, 1) as a, toDecimal32(2.1, 1) as b, divideDecimal(a, b, 1), divideDecimal(a, b, 5); +``` + +```text +┌─divide(toDecimal64(-12, 1), toDecimal32(2.1, 1))─┐ +│ -5.7 │ +└──────────────────────────────────────────────────┘ + +┌───a─┬───b─┬─divideDecimal(toDecimal64(-12, 1), toDecimal32(2.1, 1), 1)─┬─divideDecimal(toDecimal64(-12, 1), toDecimal32(2.1, 1), 5)─┐ +│ -12 │ 2.1 │ -5.7 │ -5.71428 │ +└─────┴─────┴────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┘ +``` + +```sql +SELECT toDecimal64(-12, 0) / toDecimal32(2.1, 1); +SELECT toDecimal64(-12, 0) as a, toDecimal32(2.1, 1) as b, divideDecimal(a, b, 1), divideDecimal(a, b, 5); +``` + +```text +DB::Exception: Decimal result's scale is less than argument's one: While processing toDecimal64(-12, 0) / toDecimal32(2.1, 1). (ARGUMENT_OUT_OF_BOUND) + +┌───a─┬───b─┬─divideDecimal(toDecimal64(-12, 0), toDecimal32(2.1, 1), 1)─┬─divideDecimal(toDecimal64(-12, 0), toDecimal32(2.1, 1), 5)─┐ +│ -12 │ 2.1 │ -5.7 │ -5.71428 │ +└─────┴─────┴────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┘ +``` diff --git a/docs/ru/sql-reference/functions/arithmetic-functions.md b/docs/ru/sql-reference/functions/arithmetic-functions.md index bc1d0a55128..4e040edcc70 100644 --- a/docs/ru/sql-reference/functions/arithmetic-functions.md +++ b/docs/ru/sql-reference/functions/arithmetic-functions.md @@ -159,3 +159,150 @@ SELECT min2(-1, 2); └─────────────┘ ``` +## multiplyDecimal(a, b[, result_scale]) + +Совершает умножение двух Decimal. Результат будет иметь тип [Decimal256](../../sql-reference/data-types/decimal.md). +Scale (размер дробной части) результат можно явно задать аргументом `result_scale` (целочисленная константа из интервала `[0, 76]`). +Если этот аргумент не задан, то scale результата будет равен наибольшему из scale обоих аргументов. + +**Синтаксис** + +```sql +multiplyDecimal(a, b[, result_scale]) +``` + +:::note +Эта функция работают гораздо медленнее обычной `multiply`. +В случае, если нет необходимости иметь фиксированную точность и/или нужны быстрые вычисления, следует использовать [multiply](#multiply). +::: + +**Аргументы** + +- `a` — Первый сомножитель/делимое: [Decimal](../../sql-reference/data-types/decimal.md). +- `b` — Второй сомножитель/делитель: [Decimal](../../sql-reference/data-types/decimal.md). +- `result_scale` — Scale результата: [Int/UInt](../../sql-reference/data-types/int-uint.md). + +**Возвращаемое значение** + +- Результат умножения с заданным scale. + +Тип: [Decimal256](../../sql-reference/data-types/decimal.md). + +**Примеры** + +```sql +SELECT multiplyDecimal(toDecimal256(-12, 0), toDecimal32(-2.1, 1), 1); +``` + +```text +┌─multiplyDecimal(toDecimal256(-12, 0), toDecimal32(-2.1, 1), 1)─┐ +│ 25.2 │ +└────────────────────────────────────────────────────────────────┘ +``` + +**Отличие от стандартных функций** +```sql +SELECT toDecimal64(-12.647, 3) * toDecimal32(2.1239, 4); +SELECT toDecimal64(-12.647, 3) as a, toDecimal32(2.1239, 4) as b, multiplyDecimal(a, b); +``` + +```text +┌─multiply(toDecimal64(-12.647, 3), toDecimal32(2.1239, 4))─┐ +│ -26.8609633 │ +└───────────────────────────────────────────────────────────┘ +┌─multiplyDecimal(toDecimal64(-12.647, 3), toDecimal32(2.1239, 4))─┐ +│ -26.8609 │ +└──────────────────────────────────────────────────────────────────┘ +``` + +```sql +SELECT + toDecimal64(-12.647987876, 9) AS a, + toDecimal64(123.967645643, 9) AS b, + multiplyDecimal(a, b); + +SELECT + toDecimal64(-12.647987876, 9) AS a, + toDecimal64(123.967645643, 9) AS b, + a * b; +``` + +```text +┌─────────────a─┬─────────────b─┬─multiplyDecimal(toDecimal64(-12.647987876, 9), toDecimal64(123.967645643, 9))─┐ +│ -12.647987876 │ 123.967645643 │ -1567.941279108 │ +└───────────────┴───────────────┴───────────────────────────────────────────────────────────────────────────────┘ + +Received exception from server (version 22.11.1): +Code: 407. DB::Exception: Received from localhost:9000. DB::Exception: Decimal math overflow: While processing toDecimal64(-12.647987876, 9) AS a, toDecimal64(123.967645643, 9) AS b, a * b. (DECIMAL_OVERFLOW) +``` + +## divideDecimal(a, b[, result_scale]) + +Совершает деление двух Decimal. Результат будет иметь тип [Decimal256](../../sql-reference/data-types/decimal.md). +Scale (размер дробной части) результат можно явно задать аргументом `result_scale` (целочисленная константа из интервала `[0, 76]`). +Если этот аргумент не задан, то scale результата будет равен наибольшему из scale обоих аргументов. + +**Синтаксис** + +```sql +divideDecimal(a, b[, result_scale]) +``` + +:::note +Эта функция работает гораздо медленнее обычной `divide`. +В случае, если нет необходимости иметь фиксированную точность и/или нужны быстрые вычисления, следует использовать [divide](#divide). +::: + +**Аргументы** + +- `a` — Первый сомножитель/делимое: [Decimal](../../sql-reference/data-types/decimal.md). +- `b` — Второй сомножитель/делитель: [Decimal](../../sql-reference/data-types/decimal.md). +- `result_scale` — Scale результата: [Int/UInt](../../sql-reference/data-types/int-uint.md). + +**Возвращаемое значение** + +- Результат деления с заданным scale. + +Тип: [Decimal256](../../sql-reference/data-types/decimal.md). + +**Примеры** + +```sql +SELECT divideDecimal(toDecimal256(-12, 0), toDecimal32(2.1, 1), 10); +``` + +```text +┌─divideDecimal(toDecimal256(-12, 0), toDecimal32(2.1, 1), 10)─┐ +│ -5.7142857142 │ +└──────────────────────────────────────────────────────────────┘ +``` + +**Отличие от стандартных функций** +```sql +SELECT toDecimal64(-12, 1) / toDecimal32(2.1, 1); +SELECT toDecimal64(-12, 1) as a, toDecimal32(2.1, 1) as b, divideDecimal(a, b, 1), divideDecimal(a, b, 5); +``` + +```text +┌─divide(toDecimal64(-12, 1), toDecimal32(2.1, 1))─┐ +│ -5.7 │ +└──────────────────────────────────────────────────┘ + +┌───a─┬───b─┬─divideDecimal(toDecimal64(-12, 1), toDecimal32(2.1, 1), 1)─┬─divideDecimal(toDecimal64(-12, 1), toDecimal32(2.1, 1), 5)─┐ +│ -12 │ 2.1 │ -5.7 │ -5.71428 │ +└─────┴─────┴────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┘ +``` + +```sql +SELECT toDecimal64(-12, 0) / toDecimal32(2.1, 1); +SELECT toDecimal64(-12, 0) as a, toDecimal32(2.1, 1) as b, divideDecimal(a, b, 1), divideDecimal(a, b, 5); +``` + +```text +DB::Exception: Decimal result's scale is less than argument's one: While processing toDecimal64(-12, 0) / toDecimal32(2.1, 1). (ARGUMENT_OUT_OF_BOUND) + +┌───a─┬───b─┬─divideDecimal(toDecimal64(-12, 0), toDecimal32(2.1, 1), 1)─┬─divideDecimal(toDecimal64(-12, 0), toDecimal32(2.1, 1), 5)─┐ +│ -12 │ 2.1 │ -5.7 │ -5.71428 │ +└─────┴─────┴────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┘ +``` + diff --git a/src/Functions/FunctionsDecimalArithmetics.cpp b/src/Functions/FunctionsDecimalArithmetics.cpp new file mode 100644 index 00000000000..f275f169914 --- /dev/null +++ b/src/Functions/FunctionsDecimalArithmetics.cpp @@ -0,0 +1,17 @@ +#include +#include + +namespace DB +{ +REGISTER_FUNCTION(DivideDecimals) +{ + factory.registerFunction>(Documentation( + "Decimal division with given precision. Slower than simple `divide`, but has controlled precision and no sound overflows")); +} + +REGISTER_FUNCTION(MultiplyDecimals) +{ + factory.registerFunction>(Documentation( + "Decimal multiplication with given precision. Slower than simple `divide`, but has controlled precision and no sound overflows")); +} +} diff --git a/src/Functions/FunctionsDecimalArithmetics.h b/src/Functions/FunctionsDecimalArithmetics.h new file mode 100644 index 00000000000..9806d13ed30 --- /dev/null +++ b/src/Functions/FunctionsDecimalArithmetics.h @@ -0,0 +1,457 @@ +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int DECIMAL_OVERFLOW; + extern const int ILLEGAL_COLUMN; + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int ILLEGAL_DIVISION; +} + + +struct DecimalOpHelpers +{ + /* These functions perform main arithmetic logic. + * As soon as intermediate results may not fit Decimal256 (e.g. 1e36, scale 10), + * we may not operate with Decimals. Later on this big number may be shrunk (e.g. result scale is 0 in the case above). + * That's why we need to store intermediate results in a flexible extendable storage (here we use std::vector) + * Here we operate on numbers using simple digit arithmetic. + * This is the reason these functions are slower than traditional ones. + * + * Here and below we use UInt8 for storing digits (0-9 range with maximum carry of 9 will definitely fit this) + */ + static std::vector multiply(const std::vector & num1, const std::vector & num2) + { + UInt16 const len1 = num1.size(); + UInt16 const len2 = num2.size(); + if (len1 == 0 || len2 == 0) + return {0}; + + std::vector result(len1 + len2, 0); + UInt16 i_n1 = 0; + UInt16 i_n2; + + for (Int32 i = len1 - 1; i >= 0; --i) + { + UInt16 carry = 0; + i_n2 = 0; + for (Int32 j = len2 - 1; j >= 0; --j) + { + if (unlikely(i_n1 + i_n2 >= len1 + len2)) + throw DB::Exception("Numeric overflow: result bigger that Decimal256", ErrorCodes::DECIMAL_OVERFLOW); + UInt16 sum = num1[i] * num2[j] + result[i_n1 + i_n2] + carry; + carry = sum / 10; + result[i_n1 + i_n2] = sum % 10; + ++i_n2; + } + + if (carry > 0) + { + if (unlikely(i_n1 + i_n2 >= len1 + len2)) + throw DB::Exception("Numeric overflow: result bigger that Decimal256", ErrorCodes::DECIMAL_OVERFLOW); + result[i_n1 + i_n2] += carry; + } + + ++i_n1; + } + + // Maximum Int32 value exceeds 2 billion, we can safely use it for array length storing + Int32 i = static_cast(result.size() - 1); + + while (i >= 0 && result[i] == 0) + { + result.pop_back(); + --i; + } + if (i == -1) + return {0}; + + std::reverse(result.begin(), result.end()); + return result; + } + + static std::vector divide(const std::vector & number, const Int256 & divisor) + { + std::vector result; + const auto max_index = number.size() - 1; + + UInt16 idx = 0; + Int256 temp = 0; + + while (temp < divisor && max_index > idx) + { + temp = temp * 10 + number[idx]; + ++idx; + } + + if (unlikely(temp == 0)) + return {0}; + + while (max_index >= idx) + { + result.push_back(temp / divisor); + temp = (temp % divisor) * 10 + number[idx]; + ++idx; + } + result.push_back(temp / divisor); + + return result; + } + + static std::vector toDigits(Int256 x) + { + std::vector result; + if (x >= 10) + result = toDigits(x / 10); + + result.push_back(x % 10); + return result; + } + + static UInt256 fromDigits(const std::vector & digits) + { + Int256 result = 0; + Int256 scale = 0; + for (auto i = digits.rbegin(); i != digits.rend(); ++i) + { + result += DecimalUtils::scaleMultiplier(scale) * (*i); + ++scale; + } + return result; + } +}; + + +struct DivideDecimalsImpl +{ + static constexpr auto name = "divideDecimal"; + + template + static inline Decimal256 + execute(FirstType a, SecondType b, UInt16 scale_a, UInt16 scale_b, UInt16 result_scale) + { + if (b.value == 0) + throw DB::Exception("Division by zero", ErrorCodes::ILLEGAL_DIVISION); + if (a.value == 0) + return Decimal256(0); + + Int256 sign_a = a.value < 0 ? -1 : 1; + Int256 sign_b = b.value < 0 ? -1 : 1; + + std::vector a_digits = DecimalOpHelpers::toDigits(a.value * sign_a); + + while (scale_a < scale_b + result_scale) + { + a_digits.push_back(0); + ++scale_a; + } + + while (scale_a > scale_b + result_scale && !a_digits.empty()) + { + a_digits.pop_back(); + --scale_a; + } + + if (a_digits.empty()) + return Decimal256(0); + + std::vector divided = DecimalOpHelpers::divide(a_digits, b.value * sign_b); + + if (divided.size() > DecimalUtils::max_precision) + throw DB::Exception("Numeric overflow: result bigger that Decimal256", ErrorCodes::DECIMAL_OVERFLOW); + return Decimal256(sign_a * sign_b * DecimalOpHelpers::fromDigits(divided)); + } +}; + + +struct MultiplyDecimalsImpl +{ + static constexpr auto name = "multiplyDecimal"; + + template + static inline Decimal256 + execute(FirstType a, SecondType b, UInt16 scale_a, UInt16 scale_b, UInt16 result_scale) + { + if (a.value == 0 || b.value == 0) + return Decimal256(0); + + Int256 sign_a = a.value < 0 ? -1 : 1; + Int256 sign_b = b.value < 0 ? -1 : 1; + + std::vector a_digits = DecimalOpHelpers::toDigits(a.value * sign_a); + std::vector b_digits = DecimalOpHelpers::toDigits(b.value * sign_b); + + std::vector multiplied = DecimalOpHelpers::multiply(a_digits, b_digits); + + UInt16 product_scale = scale_a + scale_b; + while (product_scale < result_scale) + { + multiplied.push_back(0); + ++product_scale; + } + + while (product_scale > result_scale&& !multiplied.empty()) + { + multiplied.pop_back(); + --product_scale; + } + + if (multiplied.empty()) + return Decimal256(0); + + if (multiplied.size() > DecimalUtils::max_precision) + throw DB::Exception("Numeric overflow: result bigger that Decimal256", ErrorCodes::DECIMAL_OVERFLOW); + + return Decimal256(sign_a * sign_b * DecimalOpHelpers::fromDigits(multiplied)); + } +}; + + +template +struct Processor +{ + const Transform transform; + + explicit Processor(Transform transform_) + : transform(std::move(transform_)) + {} + + template + void NO_INLINE + vectorConstant(const FirstArgVectorType & vec_first, const SecondArgType second_value, + PaddedPODArray & vec_to, UInt16 scale_a, UInt16 scale_b, UInt16 result_scale) const + { + size_t size = vec_first.size(); + vec_to.resize(size); + + for (size_t i = 0; i < size; ++i) + vec_to[i] = transform.execute(vec_first[i], second_value, scale_a, scale_b, result_scale); + } + + template + void NO_INLINE + vectorVector(const FirstArgVectorType & vec_first, const SecondArgVectorType & vec_second, + PaddedPODArray & vec_to, UInt16 scale_a, UInt16 scale_b, UInt16 result_scale) const + { + size_t size = vec_first.size(); + vec_to.resize(size); + + for (size_t i = 0; i < size; ++i) + vec_to[i] = transform.execute(vec_first[i], vec_second[i], scale_a, scale_b, result_scale); + } + + template + void NO_INLINE + constantVector(const FirstArgType & first_value, const SecondArgVectorType & vec_second, + PaddedPODArray & vec_to, UInt16 scale_a, UInt16 scale_b, UInt16 result_scale) const + { + size_t size = vec_second.size(); + vec_to.resize(size); + + for (size_t i = 0; i < size; ++i) + vec_to[i] = transform.execute(first_value, vec_second[i], scale_a, scale_b, result_scale); + } +}; + + +template +struct DecimalArithmeticsImpl +{ + static ColumnPtr execute(Transform transform, const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type) + { + using FirstArgValueType = typename FirstArgType::FieldType; + using FirstArgColumnType = typename FirstArgType::ColumnType; + using SecondArgValueType = typename SecondArgType::FieldType; + using SecondArgColumnType = typename SecondArgType::ColumnType; + using ResultColumnType = typename ResultType::ColumnType; + + UInt16 scale_a = getDecimalScale(*arguments[0].type); + UInt16 scale_b = getDecimalScale(*arguments[1].type); + UInt16 result_scale = getDecimalScale(*result_type->getPtr()); + + auto op = Processor{std::move(transform)}; + + auto result_col = result_type->createColumn(); + auto col_to = assert_cast(result_col.get()); + + const auto * first_col = checkAndGetColumn(arguments[0].column.get()); + const auto * second_col = checkAndGetColumn(arguments[1].column.get()); + const auto * first_col_const = typeid_cast(arguments[0].column.get()); + const auto * second_col_const = typeid_cast(arguments[1].column.get()); + + if (first_col) + { + if (second_col_const) + op.vectorConstant(first_col->getData(), second_col_const->template getValue(), col_to->getData(), scale_a, scale_b, result_scale); + else + op.vectorVector(first_col->getData(), second_col->getData(), col_to->getData(), scale_a, scale_b, result_scale); + } + else if (first_col_const) + { + op.constantVector(first_col_const->template getValue(), second_col->getData(), col_to->getData(), scale_a, scale_b, result_scale); + } + else + { + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal column {} of first argument of function {}", + arguments[0].column->getName(), Transform::name); + } + + return result_col; + } +}; + + +template +class FunctionsDecimalArithmetics : public IFunction +{ +public: + static constexpr auto name = Transform::name; + static FunctionPtr create(ContextPtr) { return std::make_shared(); } + + String getName() const override + { + return name; + } + + bool isVariadic() const override { return true; } + size_t getNumberOfArguments() const override { return 0; } + bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } + + DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override + { + if (arguments.size() != 2 && arguments.size() != 3) + throw Exception("Number of arguments for function " + getName() + " does not match: 2 or 3 expected", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + if (!isDecimal(arguments[0].type) || !isDecimal(arguments[1].type)) + throw Exception("Arguments for " + getName() + " function must be Decimal", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + UInt8 scale = std::max(getDecimalScale(*arguments[0].type->getPtr()), getDecimalScale(*arguments[1].type->getPtr())); + + if (arguments.size() == 3) + { + WhichDataType which_scale(arguments[2].type.get()); + + if (!which_scale.isUInt8()) + throw Exception( + "Illegal type " + arguments[2].type->getName() + " of third argument of function " + getName() + + ". Should be constant UInt8 from range[0, 76]", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + const ColumnConst * scale_column = checkAndGetColumnConst(arguments[2].column.get()); + + if (!scale_column) + throw Exception( + "Illegal column of third argument of function " + getName() + ". Should be constant UInt8", + ErrorCodes::ILLEGAL_COLUMN); + + scale = scale_column->getValue(); + } + + /** + At compile time, result is unknown. We only know the Scale (number of fractional digits) at runtime. + Also nothing is known about size of whole part. + As in simple division/multiplication for decimals, we scale the result up, but is is explicit here and no downscale is performed. + It guarantees that result will have given scale and it can also be MANUALLY converted to other decimal types later. + **/ + if (scale > DecimalUtils::max_precision) + throw Exception("Illegal value of third argument of function " + this->getName() + ": must be integer in range [0, 76]", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return std::make_shared(DecimalUtils::max_precision, scale); + } + + bool useDefaultImplementationForConstants() const override { return true; } + ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {2}; } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t /*input_rows_count*/) const override + { + return resolveOverload(arguments, result_type); + } + +private: + //long resolver to call proper templated func + ColumnPtr resolveOverload(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type) const + { + WhichDataType which_dividend(arguments[0].type.get()); + WhichDataType which_divisor(arguments[1].type.get()); + if (which_dividend.isDecimal32()) + { + using DividendType = DataTypeDecimal32; + if (which_divisor.isDecimal32()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal64()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal128()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal256()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + } + + else if (which_dividend.isDecimal64()) + { + using DividendType = DataTypeDecimal64; + if (which_divisor.isDecimal32()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal64()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal128()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal256()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + + } + + else if (which_dividend.isDecimal128()) + { + using DividendType = DataTypeDecimal128; + if (which_divisor.isDecimal32()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal64()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal128()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal256()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + + } + + else if (which_dividend.isDecimal256()) + { + using DividendType = DataTypeDecimal256; + if (which_divisor.isDecimal32()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal64()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal128()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + else if (which_divisor.isDecimal256()) + return DecimalArithmeticsImpl::execute(Transform{}, arguments, result_type); + + } + + // the compiler is happy now + return nullptr; + } +}; + +} + diff --git a/tests/queries/0_stateless/02475_precise_decimal_arithmetics.reference b/tests/queries/0_stateless/02475_precise_decimal_arithmetics.reference new file mode 100644 index 00000000000..6ffc8602640 --- /dev/null +++ b/tests/queries/0_stateless/02475_precise_decimal_arithmetics.reference @@ -0,0 +1,23 @@ +0 +0 +0 +9999999999999999550522436926092261716351992671467843175339166479588690755584 +9999999999999999451597035424131548206707486713696660676795842648250000000000 +11.126038 +10.8 +-11.126038 +-10.8 +10.8 +1376.638914 +1403.6 +-1376.638914 +-1403.6 +1403.6 +332833500 +999 +1000 +1000 +1000 +0.1 +0.1 +0.1 diff --git a/tests/queries/0_stateless/02475_precise_decimal_arithmetics.sql b/tests/queries/0_stateless/02475_precise_decimal_arithmetics.sql new file mode 100644 index 00000000000..3bd7906c7d8 --- /dev/null +++ b/tests/queries/0_stateless/02475_precise_decimal_arithmetics.sql @@ -0,0 +1,45 @@ +-- Tags: no-fasttest + +-- check cases when one of operands is zero +SELECT divideDecimal(toDecimal32(0, 2), toDecimal128(11.123456, 6)); +SELECT divideDecimal(toDecimal64(123.123, 3), toDecimal64(0, 1)); -- { serverError 153 } +SELECT multiplyDecimal(toDecimal32(0, 2), toDecimal128(11.123456, 6)); +SELECT multiplyDecimal(toDecimal32(123.123, 3), toDecimal128(0, 1)); + +-- don't look at strange query result -- it happens due to bad float precision: toUInt256(1e38) == 99999999999999997752612184630461283328 +SELECT multiplyDecimal(toDecimal256(1e38, 0), toDecimal256(1e38, 0)); +SELECT divideDecimal(toDecimal256(1e66, 0), toDecimal256(1e-10, 10), 0); + +-- fits Decimal256, but scale is too big to fit +SELECT multiplyDecimal(toDecimal256(1e38, 0), toDecimal256(1e38, 0), 2); -- { serverError 407 } +SELECT divideDecimal(toDecimal256(1e72, 0), toDecimal256(1e-5, 5), 2); -- { serverError 407 } + +-- does not fit Decimal256 +SELECT multiplyDecimal(toDecimal256('1e38', 0), toDecimal256('1e38', 0)); -- { serverError 407 } +SELECT multiplyDecimal(toDecimal256(1e39, 0), toDecimal256(1e39, 0), 0); -- { serverError 407 } +SELECT divideDecimal(toDecimal256(1e39, 0), toDecimal256(1e-38, 39)); -- { serverError 407 } + +-- test different signs +SELECT divideDecimal(toDecimal128(123.76, 2), toDecimal128(11.123456, 6)); +SELECT divideDecimal(toDecimal32(123.123, 3), toDecimal128(11.4, 1), 2); +SELECT divideDecimal(toDecimal128(-123.76, 2), toDecimal128(11.123456, 6)); +SELECT divideDecimal(toDecimal32(123.123, 3), toDecimal128(-11.4, 1), 2); +SELECT divideDecimal(toDecimal32(-123.123, 3), toDecimal128(-11.4, 1), 2); + +SELECT multiplyDecimal(toDecimal64(123.76, 2), toDecimal128(11.123456, 6)); +SELECT multiplyDecimal(toDecimal32(123.123, 3), toDecimal128(11.4, 1), 2); +SELECT multiplyDecimal(toDecimal64(-123.76, 2), toDecimal128(11.123456, 6)); +SELECT multiplyDecimal(toDecimal32(123.123, 3), toDecimal128(-11.4, 1), 2); +SELECT multiplyDecimal(toDecimal32(-123.123, 3), toDecimal128(-11.4, 1), 2); + +-- check against non-const columns +SELECT sum(multiplyDecimal(toDecimal64(number, 1), toDecimal64(number, 5))) FROM numbers(1000); +SELECT sum(divideDecimal(toDecimal64(number, 1), toDecimal64(number, 5))) FROM (select * from numbers(1000) OFFSET 1); + +-- check against Nullable type +SELECT multiplyDecimal(toNullable(toDecimal64(10, 1)), toDecimal64(100, 5)); +SELECT multiplyDecimal(toDecimal64(10, 1), toNullable(toDecimal64(100, 5))); +SELECT multiplyDecimal(toNullable(toDecimal64(10, 1)), toNullable(toDecimal64(100, 5))); +SELECT divideDecimal(toNullable(toDecimal64(10, 1)), toDecimal64(100, 5)); +SELECT divideDecimal(toDecimal64(10, 1), toNullable(toDecimal64(100, 5))); +SELECT divideDecimal(toNullable(toDecimal64(10, 1)), toNullable(toDecimal64(100, 5)));