StarRocks pmod 函数详解:返回正余数的取模运算
【免费下载链接】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
pmod是 StarRocks 内置的数学函数,用于返回「被除数 ÷ 除数」的正余数(positive remainder),其结果的符号与除数保持一致,与同目录下的mod(结果符号与被除数一致)形成互补。本文以 pmod 官方英文文档 为骨架,结合 BE 端向量化实现 与 FE 端函数注册源码,系统讲解 pmod 的语法、类型规则、返回值语义、完整示例及其底层实现原理,帮助你正确地在 SQL 中使用取模运算处理周期性计算、分桶取余、循环索引等场景。
pmod 是什么:与取模/余数的区别
在数学中,取模(modulo)的结果通常定义为非负数。然而在多数编程语言中,%运算符保留被除数的符号,例如 C/C++、Java 中-11 % 5 == -1。StarRocks 提供两个语义不同的取余函数:
mod(dividend, divisor):结果符号与被除数一致(详见 mod 官方文档);pmod(dividend, divisor):结果符号与除数一致,当除数为正时结果恒为非负数,即「正余数」。
从 BE 端整数实现 可以清楚看到两者的关系:
modImpl直接返回a % (b + (b == 0)),保留被除数符号;pmodImpl在此基础上再做一次「加 b 再取模」变换((a % (b + (b == 0))) + b) % (b + (b == 0)),把结果搬移到与除数同号的区间内。
也就是说,pmod(a, b)的符号由b决定:b > 0时结果 ∈[0, |b|),b < 0时结果 ∈(-|b|, 0]。
函数语法与参数说明
pmod(dividend, divisor)参数定义
| 参数 | 含义 |
|---|---|
dividend | 被除数(the number to be divided) |
divisor | 除数(the number that divides) |
两个参数支持以下数据类型:
- BIGINT
- DOUBLE
注意
dividend与divisor的数据类型必须一致;若不一致,StarRocks 会进行隐式类型转换。实际使用中建议显式保持一致(例如统一使用 DOUBLE 或 BIGINT),避免隐式转换带来的精度或语义意外。
从 FE 端函数注册 可以看到,pmod是 StarRocks 内建标量函数之一(public static final String PMOD = "pmod"),可直接在 SQL 中调用,无需任何 UDF 注册步骤。
返回值规则
- 返回与
dividend相同数据类型的值; - 当
divisor为0时返回NULL。
「除数为 0 返回 NULL」这一语义在 BE 端实现 中由RValueCheckZeroImpl承担:它检查右操作数b == 0,命中后由VectorizedUnstrictBinaryFunction框架将结果置为 NULL,而不是抛错或产生硬件除零异常。
使用示例
以下示例直接取自 pmod 官方文档 并在 StarRocks 的 MySQL 兼容客户端中验证:
mysql> select pmod(3.14,3.14); +------------------+ | pmod(3.14, 3.14) | +------------------+ | 0 | +------------------+ mysql> select pmod(3,6); +------------+ | pmod(3, 6) | +------------+ | 3 | +------------+ mysql> select pmod(11,5); +-------------+ | pmod(11, 5) | +-------------+ | 1 | +-------------+ mysql> select pmod(-11,5); +--------------+ | pmod(-11, 5) | +--------------+ | 4 | +--------------+ mysql> SELECT pmod(11,-5); +--------------+ | pmod(11, -5) | +--------------+ | -4 | +--------------+ mysql> SELECT pmod(-11,-5); +---------------+ | pmod(-11, -5) | +---------------+ | -1 | +---------------+观察这组结果可以归纳出规律:
| 表达式 | 结果 | 说明 |
|---|---|---|
pmod(3.14, 3.14) | 0 | 浮点被除数,返回 DOUBLE 类型的 0 |
pmod(3, 6) | 3 | 3 < 6,正余数即被除数本身 |
pmod(11, 5) | 1 | 常规正数取模:11 = 2×5 + 1 |
pmod(-11, 5) | 4 | 除数为正,结果非负:-11 = -3×5 + 4 |
pmod(11, -5) | -4 | 除数为负,结果与除数同号:11 = -3×(-5) + (-4) |
pmod(-11, -5) | -1 | 除数为负,结果与除数同号:-11 = 2×(-5) + (-1) |
源码剖析:pmod 在 StarRocks 中如何实现
向量化入口:按类型分派
在 be/src/exprs/math_functions.h#L366-L375 中,pmod 以向量化模板函数DEFINE_VECTORIZED_FN(pmod)暴露,按列类型Type分派到两套实现:
if constexpr (Type == TYPE_FLOAT || Type == TYPE_DOUBLE) { return VectorizedUnstrictBinaryFunction<RValueCheckZeroImpl, pmodFloatImpl>::evaluate<Type>(l, r); } else { return VectorizedUnstrictBinaryFunction<RValueCheckZeroImpl, pmodImpl>::evaluate<Type>(l, r); }- FLOAT / DOUBLE 列走
pmodFloatImpl(基于 C 标准库fmod); - 其余类型(含 BIGINT)走
pmodImpl(基于整数%运算)。
两者都包裹在VectorizedUnstrictBinaryFunction<RValueCheckZeroImpl, ...>中,由RValueCheckZeroImpl统一负责「除数为 0 返回 NULL」的语义,保证了整数与浮点路径行为一致。
整数实现与 SIGFPE 防护
pmodImpl 的完整实现为:
DEFINE_BINARY_FUNCTION_WITH_IMPL(pmodImpl, a, b) { // Guard against SIGFPE: on x86 the idiv instruction raises #DE when computing // TYPE_MIN % -1 (the quotient overflows the result width). pmod(a, -1) == 0 for // every a, so short-circuit before the hardware divide. The operator path in // arithmetic_operation.h already carries this guard; mirror it for the function. if (b == -1) { return ResultType(0); } return ((a % (b + (b == 0))) + b) % (b + (b == 0)); }这段代码蕴含了两个关键工程细节:
-1除数的短路保护:x86 的idiv指令在计算TYPE_MIN % -1时会因商溢出结果位宽而触发#DE异常(表现为 SIGFPE)。数学上任意a对-1取模结果恒为0,因此实现中先行短路返回0,避免硬件除零信号。modImpl(math_functions.h#L591-L597)同样携带了这一防护。b + (b == 0)的除零兜底:当b == 0时,(b == 0)在 C++ 中求值为1,除数退化为1,从硬件层面规避了%除零;真正的「返回 NULL」语义则由外层RValueCheckZeroImpl在结果写入前拦截完成。
浮点实现
pmodFloatImpl 基于 C 标准库fmod做了同样的「正余数」变换:
DEFINE_BINARY_FUNCTION_WITH_IMPL(pmodFloatImpl, a, b) { return ::fmod(::fmod(a, (b + (b == 0))) + b, (b + (b == 0))); }先用fmod得到与a同号的余数,再加b平移、再fmod一次,把结果约束到与b同号的区间。这也解释了文档示例中pmod(3.14, 3.14)返回0而非浮点噪声值。
与 mod、fmod 的横向对比
StarRocks 在 math-functions 文档目录 中同时提供了mod与fmod,三者容易混淆:
| 函数 | 结果符号规则 | 支持类型 | 典型实现 |
|---|---|---|---|
mod(dividend, divisor) | 与被除数一致(C 风格取余) | TINYINT/SMALLINT/INT/BIGINT/LARGEINT/FLOAT/DOUBLE 等 | a % b(math_functions.h#L591-L597) |
pmod(dividend, divisor) | 与除数一致(数学风格取模) | BIGINT、DOUBLE | ((a % b) + b) % b |
fmod(dividend, divisor) | 与被除数一致的浮点余数(IEEE 754) | FLOAT、DOUBLE | C 标准库::fmod |
选择建议:
- 需要结果为非负数(如哈希分桶、循环索引、周期性任务编号)时使用
pmod; - 需要与 C/Java 的
%语义对齐时使用mod; - 需要精确的浮点余数(如三角函数周期规约)时使用
fmod。
典型应用场景与注意事项
典型场景
- 分桶与取余路由:用
pmod(hash_value, bucket_num)把任意整数值映射到[0, bucket_num)的桶号,天然规避负数键带来的负桶号问题; - 周期编号:
pmod(day_index, 7)生成稳定的 0~6 周内索引,day_index为负(如历史日期)时结果依旧落在合法区间; - 数据脱敏/抽样:
pmod(id, 100) < 10作为确定性抽样条件,结果不受 id 正负影响。
注意事项
- 类型一致性:BIGINT 与 DOUBLE 混用时依赖隐式转换,建议显式统一(例如
CAST(a AS DOUBLE)); - 除数为 0:返回
NULL,在条件判断中需配合IS NULL处理,避免NULL参与后续运算导致结果不可见; - 负数除数:
pmod在除数为负时结果也为负(如pmod(11, -5) = -4),若业务期望恒非负,应保证除数为正; - 类型覆盖范围:pmod 仅支持 BIGINT 与 DOUBLE(区别于
mod对更宽整数类型的支持),超大整数运算建议先用CAST确认类型。
小结
pmod是 StarRocks 处理取模运算时「保证符号可控」的关键函数:文档层面明确了「结果与除数同号、除数为 0 返回 NULL」的语义;BE 源码 则展示了向量化框架下整数/浮点双实现、除零兜底与 SIGFPE 防护等工程细节。需要对照阅读时,可参考英文原文档 pmod.md 或其中文译本 中文 pmod 文档,并结合 mod.md、fmod.md 做语义对比。
【免费下载链接】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),仅供参考