Skip to content

Commit 4f0b863

Browse files
rui-moJkSelf
authored andcommitted
Add CAST(real as decimal) (8575)
1 parent ddae832 commit 4f0b863

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
@@ -453,22 +453,22 @@ void CastExpr::applyIntToDecimalCastKernel(
453453
});
454454
}
455455

456-
template <typename TOutput>
457-
void CastExpr::applyDoubleToDecimalCastKernel(
456+
template <typename TInput, typename TOutput>
457+
void CastExpr::applyFloatingPointToDecimalCastKernel(
458458
const SelectivityVector& rows,
459459
const BaseVector& input,
460460
exec::EvalCtx& context,
461461
const TypePtr& toType,
462462
VectorPtr& result) {
463-
const auto doubleInput = input.as<SimpleVector<double>>();
463+
const auto floatingInput = input.as<SimpleVector<TInput>>();
464464
auto rawResults =
465465
result->asUnchecked<FlatVector<TOutput>>()->mutableRawValues();
466466
const auto toPrecisionScale = getDecimalPrecisionScale(*toType);
467467

468468
applyToSelectedNoThrowLocal(context, rows, result, [&](vector_size_t row) {
469469
TOutput output;
470-
const auto status = DecimalUtil::rescaleDouble<TOutput>(
471-
doubleInput->valueAt(row),
470+
const auto status = DecimalUtil::rescaleFloatingPoint<TInput, TOutput>(
471+
floatingInput->valueAt(row),
472472
toPrecisionScale.first,
473473
toPrecisionScale.second,
474474
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
@@ -1969,29 +1969,52 @@ TEST_F(CastExprTest, castInTry) {
19691969

19701970
TEST_F(CastExprTest, doubleToDecimal) {
19711971
// Double to short decimal.
1972-
const auto input =
1973-
makeFlatVector<double>({-3333.03, -2222.02, -1.0, 0.00, 100, 99999.99});
1972+
const auto input = makeFlatVector<double>(
1973+
{-3333.03,
1974+
-2222.02,
1975+
-1.0,
1976+
0.00,
1977+
100,
1978+
99999.99,
1979+
10.03,
1980+
10.05,
1981+
9.95,
1982+
-2.123456789});
19741983
testCast(
19751984
input,
19761985
makeFlatVector<int64_t>(
1977-
{-33'330'300, -22'220'200, -10'000, 0, 1'000'000, 999'999'900},
1986+
{-33'330'300,
1987+
-22'220'200,
1988+
-10'000,
1989+
0,
1990+
1'000'000,
1991+
999'999'900,
1992+
100'300,
1993+
100'500,
1994+
99'500,
1995+
-21'235},
19781996
DECIMAL(10, 4)));
19791997

19801998
// Double to long decimal.
19811999
testCast(
19822000
input,
19832001
makeFlatVector<int128_t>(
1984-
{-33'330'300'000'000,
1985-
-22'220'200'000'000,
1986-
-10'000'000'000,
2002+
{HugeInt::build(0xFFFFFFFFFFFFFF4B, 0x50EABA2657C90000),
2003+
HugeInt::build(0xFFFFFFFFFFFFFF87, 0x8B4726C43A860000),
2004+
-1'000'000'000'000'000'000,
19872005
0,
1988-
1'000'000'000'000,
1989-
999'999'900'000'000},
1990-
DECIMAL(20, 10)));
2006+
HugeInt::build(0x5, 0x6BC75E2D63100000),
2007+
HugeInt::build(0x152D, 0x02A45A5886BF0000),
2008+
HugeInt::build(0, 0x8B31B7DBD92B0000),
2009+
HugeInt::build(0, 0x8B78C5C0B8AD0000),
2010+
HugeInt::build(0, 0x8A1580485B230000),
2011+
-2'123'456'789'000'000'000},
2012+
DECIMAL(38, 18)));
19912013
testCast(
19922014
input,
19932015
makeFlatVector<int128_t>(
1994-
{-33'330, -22'220, -10, 0, 1'000, 1'000'000}, DECIMAL(20, 1)));
2016+
{-33'330, -22'220, -10, 0, 1'000, 1'000'000, 100, 101, 100, -21},
2017+
DECIMAL(20, 1)));
19952018
testCast(
19962019
makeNullableFlatVector<double>(
19972020
{0.13456789,
@@ -2062,6 +2085,119 @@ TEST_F(CastExprTest, doubleToDecimal) {
20622085
"Cannot cast DOUBLE 'NaN' to DECIMAL(38, 2). The input value should be finite.");
20632086
}
20642087

2088+
TEST_F(CastExprTest, realToDecimal) {
2089+
// Real to short decimal.
2090+
const auto input = makeFlatVector<float>(
2091+
{-3333.03,
2092+
-2222.02,
2093+
-1.0,
2094+
0.00,
2095+
100,
2096+
99999.9,
2097+
10.03,
2098+
10.05,
2099+
9.95,
2100+
-2.12345});
2101+
testCast(
2102+
input,
2103+
makeFlatVector<int64_t>(
2104+
{-33'330'300,
2105+
-22'220'200,
2106+
-10'000,
2107+
0,
2108+
1'000'000,
2109+
999'999'000,
2110+
100'300,
2111+
100'500,
2112+
99'500,
2113+
-212'35},
2114+
DECIMAL(10, 4)));
2115+
2116+
// Real to long decimal.
2117+
testCast(
2118+
input,
2119+
makeFlatVector<int128_t>(
2120+
{HugeInt::build(0xFFFFFFFFFFFFFF4B, 0x50EABA2657C90000),
2121+
HugeInt::build(0xFFFFFFFFFFFFFF87, 0x8B4726C43A860000),
2122+
-1'000'000'000'000'000'000,
2123+
0,
2124+
HugeInt::build(0x5, 0x6BC75E2D63100000),
2125+
HugeInt::build(0x152D, 0x01649BD298F60000),
2126+
HugeInt::build(0, 0x8B31B7DBD92B0000),
2127+
HugeInt::build(0, 0x8B78C5C0B8AD0000),
2128+
HugeInt::build(0, 0x8A1580485B230000),
2129+
-2'123'450'000'000'000'000},
2130+
DECIMAL(38, 18)));
2131+
testCast(
2132+
input,
2133+
makeFlatVector<int128_t>(
2134+
{-33'330, -22'220, -10, 0, 1'000, 999'999, 100, 101, 100, -21},
2135+
DECIMAL(20, 1)));
2136+
testCast(
2137+
makeNullableFlatVector<float>(
2138+
{0.134567, 0.000015, 0.000001, 0.999999, 0.123456, std::nullopt}),
2139+
makeNullableFlatVector<int128_t>(
2140+
{134'567'000'000'000'000,
2141+
15'000'000'000'000,
2142+
1'000'000'000'000,
2143+
999'999'000'000'000'000,
2144+
123'456'000'000'000'000,
2145+
std::nullopt},
2146+
DECIMAL(38, 18)));
2147+
2148+
testThrow<float>(
2149+
REAL(),
2150+
DECIMAL(10, 2),
2151+
{9999999999999999999999.99},
2152+
"Cannot cast REAL '9.999999778196308E21' to DECIMAL(10, 2). Result overflows.");
2153+
testThrow<float>(
2154+
REAL(),
2155+
DECIMAL(10, 2),
2156+
{static_cast<float>(
2157+
static_cast<int128_t>(std::numeric_limits<int64_t>::max()) + 1)},
2158+
"Cannot cast REAL '9223372036854776000' to DECIMAL(10, 2). Result overflows.");
2159+
testThrow<float>(
2160+
REAL(),
2161+
DECIMAL(10, 2),
2162+
{static_cast<float>(
2163+
static_cast<int128_t>(std::numeric_limits<int64_t>::min()) - 1)},
2164+
"Cannot cast REAL '-9223372036854776000' to DECIMAL(10, 2). Result overflows.");
2165+
testThrow<float>(
2166+
REAL(),
2167+
DECIMAL(20, 2),
2168+
{static_cast<float>(DecimalUtil::kLongDecimalMax)},
2169+
"Cannot cast REAL '9.999999680285692E37' to DECIMAL(20, 2). Result overflows.");
2170+
testThrow<float>(
2171+
REAL(),
2172+
DECIMAL(20, 2),
2173+
{static_cast<float>(DecimalUtil::kLongDecimalMin)},
2174+
"Cannot cast REAL '-9.999999680285692E37' to DECIMAL(20, 2). Result overflows.");
2175+
testThrow<float>(
2176+
REAL(),
2177+
DECIMAL(38, 2),
2178+
{std::numeric_limits<float>::max()},
2179+
"Cannot cast REAL '3.4028234663852886E38' to DECIMAL(38, 2). Result overflows.");
2180+
testThrow<float>(
2181+
REAL(),
2182+
DECIMAL(38, 2),
2183+
{std::numeric_limits<float>::lowest()},
2184+
"Cannot cast REAL '-3.4028234663852886E38' to DECIMAL(38, 2). Result overflows.");
2185+
testCast(
2186+
makeConstant<float>(std::numeric_limits<float>::min(), 1),
2187+
makeConstant<int128_t>(0, 1, DECIMAL(38, 2)));
2188+
2189+
testThrow<float>(
2190+
REAL(),
2191+
DECIMAL(38, 2),
2192+
{INFINITY},
2193+
"Cannot cast REAL 'Infinity' to DECIMAL(38, 2). The input value should be finite.");
2194+
testThrow<float>(
2195+
REAL(),
2196+
DECIMAL(38, 2),
2197+
{NAN},
2198+
"Cannot cast REAL 'NaN' to DECIMAL(38, 2). The input value should be finite.");
2199+
}
2200+
20652201
TEST_F(CastExprTest, primitiveNullConstant) {
20662202
// Evaluate cast(NULL::double as bigint).
20672203
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)