Skip to content

Commit 4dd1664

Browse files
rui-moJkSelf
authored andcommitted
Add CAST(real as decimal) (8575)
1 parent 2928da1 commit 4dd1664

File tree

7 files changed

+381
-94
lines changed

7 files changed

+381
-94
lines changed

velox/docs/functions/presto/conversion.rst

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ supported conversions to/from JSON are listed in :doc:`json`.
123123
-
124124
-
125125
-
126-
-
126+
- Y
127127
* - double
128128
- Y
129129
- Y
@@ -724,14 +724,15 @@ Invalid examples
724724
SELECT cast(123 as decimal(6, 4)); -- Out of range
725725
SELECT cast(123 as decimal(4, 2)); -- Out of range
726726

727-
From double type
728-
^^^^^^^^^^^^^^^^
727+
From floating-point types
728+
^^^^^^^^^^^^^^^^^^^^^^^^^
729729

730-
Casting a double number to a decimal of given precision and scale is allowed
731-
if the input value can be represented by the precision and scale. When the
732-
given scale is less than the number of decimal places, the double value is
733-
rounded. The conversion precision is up to 15 as double provides 16(±1)
734-
significant decimal digits precision. Casting from invalid input values throws.
730+
Casting a floating-point number to a decimal of given precision and scale is allowed
731+
if the input value can be represented by the precision and scale. When the given
732+
scale is less than the number of decimal places, the floating-point value is rounded.
733+
The conversion precision is up to 15 for double and 6 for real according to the
734+
significant decimal digits precision they provide. Casting from invalid input values
735+
throws.
735736

736737
Valid example
737738

@@ -741,6 +742,7 @@ Valid example
741742
SELECT cast(0.12 as decimal(4, 1)); -- decimal '0.1'
742743
SELECT cast(0.19 as decimal(4, 1)); -- decimal '0.2'
743744
SELECT cast(0.123456789123123 as decimal(38, 18)); -- decimal '0.123456789123123000'
745+
SELECT cast(cast(0.123456 as real) as decimal(38, 18)); -- decimal '0.123456000000000000'
744746

745747
Invalid example
746748

velox/expression/CastExpr-inl.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -460,22 +460,22 @@ void CastExpr::applyIntToDecimalCastKernel(
460460
});
461461
}
462462

463-
template <typename TOutput>
464-
void CastExpr::applyDoubleToDecimalCastKernel(
463+
template <typename TInput, typename TOutput>
464+
void CastExpr::applyFloatingPointToDecimalCastKernel(
465465
const SelectivityVector& rows,
466466
const BaseVector& input,
467467
exec::EvalCtx& context,
468468
const TypePtr& toType,
469469
VectorPtr& result) {
470-
const auto doubleInput = input.as<SimpleVector<double>>();
470+
const auto floatingInput = input.as<SimpleVector<TInput>>();
471471
auto rawResults =
472472
result->asUnchecked<FlatVector<TOutput>>()->mutableRawValues();
473473
const auto toPrecisionScale = getDecimalPrecisionScale(*toType);
474474

475475
applyToSelectedNoThrowLocal(context, rows, result, [&](vector_size_t row) {
476476
TOutput output;
477-
const auto status = DecimalUtil::rescaleDouble<TOutput>(
478-
doubleInput->valueAt(row),
477+
const auto status = DecimalUtil::rescaleFloatingPoint<TInput, TOutput>(
478+
floatingInput->valueAt(row),
479479
toPrecisionScale.first,
480480
toPrecisionScale.second,
481481
output);

velox/expression/CastExpr.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,12 @@ VectorPtr CastExpr::applyDecimal(
576576
applyIntToDecimalCastKernel<int32_t, toDecimalType>(
577577
rows, input, context, toType, castResult);
578578
break;
579+
case TypeKind::REAL:
580+
applyFloatingPointToDecimalCastKernel<float, toDecimalType>(
581+
rows, input, context, toType, castResult);
582+
break;
579583
case TypeKind::DOUBLE:
580-
applyDoubleToDecimalCastKernel<toDecimalType>(
584+
applyFloatingPointToDecimalCastKernel<double, toDecimalType>(
581585
rows, input, context, toType, castResult);
582586
break;
583587
case TypeKind::BIGINT: {

velox/expression/CastExpr.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ class CastExpr : public SpecialForm {
206206
const TypePtr& toType,
207207
VectorPtr& castResult);
208208

209-
template <typename TOutput>
210-
void applyDoubleToDecimalCastKernel(
209+
template <typename TInput, typename TOutput>
210+
void applyFloatingPointToDecimalCastKernel(
211211
const SelectivityVector& rows,
212212
const BaseVector& input,
213213
exec::EvalCtx& context,

velox/expression/tests/CastExprTest.cpp

Lines changed: 146 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,29 +1992,52 @@ TEST_F(CastExprTest, castInTry) {
19921992

19931993
TEST_F(CastExprTest, doubleToDecimal) {
19941994
// Double to short decimal.
1995-
const auto input =
1996-
makeFlatVector<double>({-3333.03, -2222.02, -1.0, 0.00, 100, 99999.99});
1995+
const auto input = makeFlatVector<double>(
1996+
{-3333.03,
1997+
-2222.02,
1998+
-1.0,
1999+
0.00,
2000+
100,
2001+
99999.99,
2002+
10.03,
2003+
10.05,
2004+
9.95,
2005+
-2.123456789});
19972006
testCast(
19982007
input,
19992008
makeFlatVector<int64_t>(
2000-
{-33'330'300, -22'220'200, -10'000, 0, 1'000'000, 999'999'900},
2009+
{-33'330'300,
2010+
-22'220'200,
2011+
-10'000,
2012+
0,
2013+
1'000'000,
2014+
999'999'900,
2015+
100'300,
2016+
100'500,
2017+
99'500,
2018+
-21'235},
20012019
DECIMAL(10, 4)));
20022020

20032021
// Double to long decimal.
20042022
testCast(
20052023
input,
20062024
makeFlatVector<int128_t>(
2007-
{-33'330'300'000'000,
2008-
-22'220'200'000'000,
2009-
-10'000'000'000,
2025+
{HugeInt::build(0xFFFFFFFFFFFFFF4B, 0x50EABA2657C90000),
2026+
HugeInt::build(0xFFFFFFFFFFFFFF87, 0x8B4726C43A860000),
2027+
-1'000'000'000'000'000'000,
20102028
0,
2011-
1'000'000'000'000,
2012-
999'999'900'000'000},
2013-
DECIMAL(20, 10)));
2029+
HugeInt::build(0x5, 0x6BC75E2D63100000),
2030+
HugeInt::build(0x152D, 0x02A45A5886BF0000),
2031+
HugeInt::build(0, 0x8B31B7DBD92B0000),
2032+
HugeInt::build(0, 0x8B78C5C0B8AD0000),
2033+
HugeInt::build(0, 0x8A1580485B230000),
2034+
-2'123'456'789'000'000'000},
2035+
DECIMAL(38, 18)));
20142036
testCast(
20152037
input,
20162038
makeFlatVector<int128_t>(
2017-
{-33'330, -22'220, -10, 0, 1'000, 1'000'000}, DECIMAL(20, 1)));
2039+
{-33'330, -22'220, -10, 0, 1'000, 1'000'000, 100, 101, 100, -21},
2040+
DECIMAL(20, 1)));
20182041
testCast(
20192042
makeNullableFlatVector<double>(
20202043
{0.13456789,
@@ -2085,6 +2108,119 @@ TEST_F(CastExprTest, doubleToDecimal) {
20852108
"Cannot cast DOUBLE 'NaN' to DECIMAL(38, 2). The input value should be finite.");
20862109
}
20872110

2111+
TEST_F(CastExprTest, realToDecimal) {
2112+
// Real to short decimal.
2113+
const auto input = makeFlatVector<float>(
2114+
{-3333.03,
2115+
-2222.02,
2116+
-1.0,
2117+
0.00,
2118+
100,
2119+
99999.9,
2120+
10.03,
2121+
10.05,
2122+
9.95,
2123+
-2.12345});
2124+
testCast(
2125+
input,
2126+
makeFlatVector<int64_t>(
2127+
{-33'330'300,
2128+
-22'220'200,
2129+
-10'000,
2130+
0,
2131+
1'000'000,
2132+
999'999'000,
2133+
100'300,
2134+
100'500,
2135+
99'500,
2136+
-212'35},
2137+
DECIMAL(10, 4)));
2138+
2139+
// Real to long decimal.
2140+
testCast(
2141+
input,
2142+
makeFlatVector<int128_t>(
2143+
{HugeInt::build(0xFFFFFFFFFFFFFF4B, 0x50EABA2657C90000),
2144+
HugeInt::build(0xFFFFFFFFFFFFFF87, 0x8B4726C43A860000),
2145+
-1'000'000'000'000'000'000,
2146+
0,
2147+
HugeInt::build(0x5, 0x6BC75E2D63100000),
2148+
HugeInt::build(0x152D, 0x01649BD298F60000),
2149+
HugeInt::build(0, 0x8B31B7DBD92B0000),
2150+
HugeInt::build(0, 0x8B78C5C0B8AD0000),
2151+
HugeInt::build(0, 0x8A1580485B230000),
2152+
-2'123'450'000'000'000'000},
2153+
DECIMAL(38, 18)));
2154+
testCast(
2155+
input,
2156+
makeFlatVector<int128_t>(
2157+
{-33'330, -22'220, -10, 0, 1'000, 999'999, 100, 101, 100, -21},
2158+
DECIMAL(20, 1)));
2159+
testCast(
2160+
makeNullableFlatVector<float>(
2161+
{0.134567, 0.000015, 0.000001, 0.999999, 0.123456, std::nullopt}),
2162+
makeNullableFlatVector<int128_t>(
2163+
{134'567'000'000'000'000,
2164+
15'000'000'000'000,
2165+
1'000'000'000'000,
2166+
999'999'000'000'000'000,
2167+
123'456'000'000'000'000,
2168+
std::nullopt},
2169+
DECIMAL(38, 18)));
2170+
2171+
testThrow<float>(
2172+
REAL(),
2173+
DECIMAL(10, 2),
2174+
{9999999999999999999999.99},
2175+
"Cannot cast REAL '9.999999778196308E21' to DECIMAL(10, 2). Result overflows.");
2176+
testThrow<float>(
2177+
REAL(),
2178+
DECIMAL(10, 2),
2179+
{static_cast<float>(
2180+
static_cast<int128_t>(std::numeric_limits<int64_t>::max()) + 1)},
2181+
"Cannot cast REAL '9223372036854776000' to DECIMAL(10, 2). Result overflows.");
2182+
testThrow<float>(
2183+
REAL(),
2184+
DECIMAL(10, 2),
2185+
{static_cast<float>(
2186+
static_cast<int128_t>(std::numeric_limits<int64_t>::min()) - 1)},
2187+
"Cannot cast REAL '-9223372036854776000' to DECIMAL(10, 2). Result overflows.");
2188+
testThrow<float>(
2189+
REAL(),
2190+
DECIMAL(20, 2),
2191+
{static_cast<float>(DecimalUtil::kLongDecimalMax)},
2192+
"Cannot cast REAL '9.999999680285692E37' to DECIMAL(20, 2). Result overflows.");
2193+
testThrow<float>(
2194+
REAL(),
2195+
DECIMAL(20, 2),
2196+
{static_cast<float>(DecimalUtil::kLongDecimalMin)},
2197+
"Cannot cast REAL '-9.999999680285692E37' to DECIMAL(20, 2). Result overflows.");
2198+
testThrow<float>(
2199+
REAL(),
2200+
DECIMAL(38, 2),
2201+
{std::numeric_limits<float>::max()},
2202+
"Cannot cast REAL '3.4028234663852886E38' to DECIMAL(38, 2). Result overflows.");
2203+
testThrow<float>(
2204+
REAL(),
2205+
DECIMAL(38, 2),
2206+
{std::numeric_limits<float>::lowest()},
2207+
"Cannot cast REAL '-3.4028234663852886E38' to DECIMAL(38, 2). Result overflows.");
2208+
testCast(
2209+
makeConstant<float>(std::numeric_limits<float>::min(), 1),
2210+
makeConstant<int128_t>(0, 1, DECIMAL(38, 2)));
2211+
2212+
testThrow<float>(
2213+
REAL(),
2214+
DECIMAL(38, 2),
2215+
{INFINITY},
2216+
"Cannot cast REAL 'Infinity' to DECIMAL(38, 2). The input value should be finite.");
2217+
testThrow<float>(
2218+
REAL(),
2219+
DECIMAL(38, 2),
2220+
{NAN},
2221+
"Cannot cast REAL 'NaN' to DECIMAL(38, 2). The input value should be finite.");
2222+
}
2223+
20882224
TEST_F(CastExprTest, primitiveNullConstant) {
20892225
// Evaluate cast(NULL::double as bigint).
20902226
auto cast =

velox/type/DecimalUtil.h

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <string>
2020
#include "velox/common/base/CheckedArithmetic.h"
21+
#include "velox/common/base/CountBits.h"
2122
#include "velox/common/base/Exceptions.h"
2223
#include "velox/common/base/Nulls.h"
2324
#include "velox/common/base/Status.h"
@@ -203,29 +204,55 @@ class DecimalUtil {
203204
return static_cast<TOutput>(rescaledValue);
204205
}
205206

206-
/// Rescales a double value to decimal value of given precision and scale. The
207-
/// output is rescaled value of int128_t or int64_t type. Returns error status
208-
/// if fails.
209-
template <typename TOutput>
210-
inline static Status
211-
rescaleDouble(double value, int precision, int scale, TOutput& output) {
207+
/// Rescales a floating point value to decimal value of given precision and
208+
/// scale. The output is rescaled value of int128_t or int64_t type. Returns
209+
/// error status if fails.
210+
template <typename TIntput, typename TOutput>
211+
inline static Status rescaleFloatingPoint(
212+
TIntput value,
213+
int precision,
214+
int scale,
215+
TOutput& output) {
212216
if (!std::isfinite(value)) {
213217
return Status::UserError("The input value should be finite.");
214218
}
215219

220+
uint8_t digits;
221+
if constexpr (std::is_same_v<TIntput, float>) {
222+
// A float provides between 6 and 7 decimal digits, so at least 6 digits
223+
// are precise.
224+
digits = 6;
225+
} else {
226+
// A double provides from 15 to 17 decimal digits, so at least 15 digits
227+
// are precise.
228+
digits = 15;
229+
if (value <= std::numeric_limits<int128_t>::min() ||
230+
value >= std::numeric_limits<int128_t>::max()) {
231+
return Status::UserError("Result overflows.");
232+
}
233+
}
234+
235+
// Calculate the precise fractional digits.
236+
const auto integralValue =
237+
static_cast<uint128_t>(value > 0 ? value : -value);
238+
const auto integralDigits =
239+
integralValue == 0 ? 0 : countDigits(integralValue);
240+
const auto fractionDigits = digits - integralDigits;
241+
/// Scales up the input value to keep all the precise fractional digits
242+
/// before rounding. Convert value to long double type, as double * int128_t
243+
/// returns int128_t and fractional digits are lost. No need to consider
244+
/// 'toValue' becoming infinite as DOUBLE_MAX * 10^38 < LONG_DOUBLE_MAX.
245+
const auto scaledValue =
246+
(long double)value * DecimalUtil::kPowersOfTen[fractionDigits];
247+
216248
long double rounded;
217-
// A double provides 16(±1) decimal digits, so at least 15 digits are
218-
// precise.
219-
if (scale > 15) {
220-
// Convert value to long double type, as double * int128_t returns
221-
// int128_t and fractional digits are lost. No need to consider 'toValue'
222-
// becoming infinite as DOUBLE_MAX * 10^38 < LONG_DOUBLE_MAX.
223-
const auto toValue = (long double)value * DecimalUtil::kPowersOfTen[15];
224-
rounded = std::round(toValue) * DecimalUtil::kPowersOfTen[scale - 15];
249+
if (scale > fractionDigits) {
250+
rounded = std::round(scaledValue) *
251+
DecimalUtil::kPowersOfTen[scale - fractionDigits];
225252
} else {
226-
const auto toValue =
227-
(long double)value * DecimalUtil::kPowersOfTen[scale];
228-
rounded = std::round(toValue);
253+
rounded = std::round(
254+
std::round(scaledValue) /
255+
DecimalUtil::kPowersOfTen[fractionDigits - scale]);
229256
}
230257

231258
const auto result = folly::tryTo<TOutput>(rounded);

0 commit comments

Comments
 (0)