StarRocks truncate 函数详解:向零方向截断小数位的数值处理实战
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
导读
truncate是 StarRocks 提供的数学函数之一,用于将输入数值向零方向(即向下取整到更小或相等的值)截断到小数点后指定位数,与四舍五入的round行为截然不同。本文以官方函数文档 truncate.md 为骨架,结合 BE 端向量化实现 math_functions.cpp 与单元测试 math_functions_test.cpp,讲解其语法、参数、返回类型、边界行为与底层实现原理,帮助你在报表清洗、金额精度处理和指标聚合等场景中正确使用截断语义。
函数功能与核心语义
truncate对输入数值执行截断(truncation)而非四舍五入:它去掉小数点后超出指定精度的数字部分,且不进行进位。
- 对正数,结果等于向下取整(floor)到指定精度,即结果 ≤ 原值;
- 对负数,结果同样向零方向靠拢,即结果 ≥ 原值(绝对值变小);
- 返回值的类型与第一个参数
arg1的类型保持一致。
官方文档给出的定位是:Rounds the input down to the nearest equal or smaller value with the specified number of places after the decimal point,即“向最接近的、小于等于输入值的方向舍入,并保留小数点后指定位数”。
语法
函数签名如下:
truncate(arg1, arg2);其中:
arg1:待截断的输入数值,支持两种数据类型:- DOUBLE(双精度浮点)
- DECIMAL128(高精度定点小数)
arg2:小数点后需要保留的位数,数据类型为INT(整数)。
说明:DECIMAL128 对应 StarRocks 中精度最高可达 38 位的十进制类型(DECIMAL(38, scale)),用于需要精确十进制运算的金融、计量类场景;DOUBLE 则用于普通浮点计算场景。
返回值
truncate返回与arg1相同数据类型的值:
- 若
arg1为 DOUBLE,则返回 DOUBLE; - 若
arg1为 DECIMAL128,则返回同精度/标度的 DECIMAL128 值。
从 FE 侧注册信息看,truncate与round、dround一起被归类为 DECIMAL 舍入函数集合DECIMAL_ROUND_FUNCTIONS(见 FunctionSet.java),在 Decimal 类型上统一走 DECIMAL 舍入路径。
使用示例
基础示例(来自官方文档)
mysql> select truncate(3.14,1); +-------------------+ | truncate(3.14, 1) | +-------------------+ | 3.1 | +-------------------+ 1 row in set (0.00 sec)3.14截断到小数点后 1 位得到3.1——注意,四舍五入的结果应为3.1,此处恰好一致;若输入3.19,则truncate(3.19, 1)返回3.1而round(3.19, 1)返回3.2,两者区别立即显现。
更多实战示例
-- 正数截断:直接丢弃多余小数位 mysql> select truncate(2341.2341111, 2); +--------------------------+ | truncate(2341.2341111,2) | +--------------------------+ | 2341.23 | +--------------------------+ -- 负数截断:向零方向,结果大于原值(绝对值变小) mysql> select truncate(-3.99, 1); +--------------------+ | truncate(-3.99, 1) | +--------------------+ | -3.9 | +--------------------+ -- 第二位参数为 0:保留整数部分 mysql> select truncate(3.99, 0); +-------------------+ | truncate(3.99, 0) | +-------------------+ | 3 | +-------------------+这些用例与 BE 端单测truncateTest中的断言一致(见 math_functions_test.cpp):2341.2341111 → 2341.23、4999.90134 → 4999.901、2144.2855 → 2144.2、934.12439 → 934.1243,均验证了“直接截断、不进位”的行为。
底层实现原理
DOUBLE 类型:统一走 double_round 截断分支
对于 DOUBLE 输入,BE 端定义如下(见 math_functions.cpp):
DEFINE_BINARY_FUNCTION_WITH_IMPL(truncateImpl, l, r) { return MathFunctions::double_round(l, r, false, true); }即调用double_round(value, dec, dec_unsigned=false, truncate=true),其中最后一个布尔参数truncate决定走截断分支还是四舍五入分支。在double_round的实现中(math_functions.cpp):
} else if (truncate) { if (value >= 0.0) { tmp2 = dec < 0 ? std::floor(value_div_tmp) * tmp : std::floor(value_mul_tmp) / tmp; } else { tmp2 = dec < 0 ? std::ceil(value_div_tmp) * tmp : std::ceil(value_mul_tmp) / tmp; } }关键点:
- 正数用
std::floor(向下取整),负数用std::ceil(向上取整),从而实现向零方向截断; dec可以为负值:当arg2 < 0时,表示在小数点左侧截断,例如truncate(134.56, -1)会按floor(134.56 / 10) * 10得到130;- 实现中通过
log_10[]预计算表加速10^|dec|,避免每次调用都执行std::pow(见 math_functions.cpp); - 使用
volatile中间变量避免编译器过度优化导致 80 位扩展精度带来的结果不一致(见源码注释,math_functions.cpp)。
DOUBLE 版本的函数注册还带有 NaN 检查:DEFINE_MATH_BINARY_FN_WITH_NAN_CHECK(truncate, TYPE_DOUBLE, TYPE_INT, TYPE_DOUBLE)(math_functions.cpp),即输入或计算结果出现 NaN 时返回 NULL。
DECIMAL128 类型:走 DecimalV3 舍入框架
对于 DECIMAL128 输入,truncate被路由到truncate_decimal128(math_functions.cpp):
StatusOr<ColumnPtr> MathFunctions::truncate_decimal128(FunctionContext* context, const Columns& columns) { return decimal_round<DecimalRoundRule::ROUND_TRUNCATE>(context, columns); }底层decimal_round<ROUND_TRUNCATE>(math_functions.cpp)基于 DecimalV3 的DecimalV3Cast::round模板实现,规则为ROUND_TRUNCATE。其内部逻辑包括:
- 标度(scale)调整:计算
target_scale = arg2与original_scale的差值scale_diff,据此决定放大或缩小后舍入; - 保留标度(keep_scale)分支:当
arg2为非常量(逐行取值)时,结果保持原始标度(例如1.2345截断到 2 位后再还原标度得到1.2300),见 math_functions.cpp; - 溢出处理:当
scale_diff绝对值超过 DECIMAL128 最大精度(38)时标记溢出,返回 NULL(math_functions.cpp); - 常量折叠优化:对“两参数均为常量列”“仅第一参数为常量”“仅第二参数为常量”等场景分别做了向量化分支,最大化列式计算性能(math_functions.cpp)。
空值与异常行为
- 任一参数为 NULL:结果为空值(NULL),BE 端通过
RETURN_IF_COLUMNS_ONLY_NULL和空值标记联合逻辑处理(见 math_functions.cpp); - 结果溢出:DECIMAL 截断结果超出目标精度时返回 NULL;
- NaN 输入:DOUBLE 场景下返回 NULL,单测
truncateNanTest以truncate(0, 1591994755)验证了极大位数下结果为 NULL 的行为(math_functions_test.cpp)。
truncate 与 round 的取舍
| 场景 | truncate(截断) | round(四舍五入) |
|---|---|---|
3.19保留 1 位 | 3.1(直接丢弃) | 3.2(进位) |
-3.19保留 1 位 | -3.1(向零) | -3.2(远离零) |
| 适用场景 | 金额分账、物理量截取、维度归一 | 常规统计舍入、展示精度 |
从源码看,二者共用同一套double_round与decimal_round框架,仅通过布尔标志truncate或模板规则ROUND_TRUNCATE/ROUND_HALF_UP区分(math_functions.cpp),因此性能开销一致,可按业务语义自由选择。
注意事项与最佳实践
- 不要用 truncate 做货币四舍五入:金融场景若需要“四舍五入到分”,应使用
round,否则会损失进位金额; - 负的第二参数:
truncate(x, -n)可在整数位上进行截断,例如truncate(1234.56, -2)得到1200,适用于数值分桶、量级归一化; - 类型一致性:返回类型跟随
arg1,若希望返回 DECIMAL 而非 DOUBLE,请显式 CAST 输入; - 浮点精度提醒:DOUBLE 输入本身存在浮点误差,对精度敏感的场景建议改用 DECIMAL128 类型。
相关阅读
- 函数官方文档:truncate.md
- BE 端核心实现:math_functions.cpp(
double_round于 L649,decimal_round于 L802) - 单元测试:math_functions_test.cpp
- FE 端函数注册与 DECIMAL 舍入归类:FunctionSet.java
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考