- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
本文是仓颉(Cangjie)编程语言标准库
std.overflow包接口文档的技术导读,完整梳理了CheckedOp、SaturatingOp、ThrowingOp、WrappingOp、CarryingOp五类溢出处理接口及其幂运算扩展接口(CheckedPow、SaturatingPow、ThrowingPow、WrappingPow、CarryingPow)的签名、行为语义与使用方式。读完本文,你将掌握仓颉中整数溢出的五种处理策略、如何在Int8到UInt64的全部整数类型上调用这些 API,并能结合实际示例理解溢出场景下的返回值与异常行为。
一、overflow 包概述:为什么需要显式的溢出策略
在仓颉语言中,整数运算一旦出现溢出(运算结果大于类型最大值或小于类型最小值),默认行为是抛出异常。这在绝大多数场景下是安全的选择,但部分领域(如哈希计算、循环冗余校验、位图算法、密码学实现、协议字段编解码)需要显式控制溢出行为:要么检测、要么忽略、要么钳制到边界值。
std.overflow包正是为此而生。它定义了四种溢出处理策略,并通过接口 + 类型扩展的方式为所有整数类型统一提供了对应的方法:
| 策略 | 接口 | 描述 |
|---|---|---|
| 返回 Option | CheckedOp | 当整数运算出现溢出,返回None |
| 饱和 | SaturatingOp | 当计算结果大于目标类型的 MAX 值,返回 MAX 值;当计算结果小于目标类型的 MIN 值,返回 MIN 值 |
| 抛出异常 | ThrowingOp | 当整数运算出现溢出,抛出异常 |
| 高位截断 | WrappingOp | 当整数运算出现溢出,将运算结果中超出目标类型位数的高位截断 |
除此之外,包中还提供了CarryingOp这一"返回截断标志 + 结果"的组合式接口,以及一系列对应的幂运算接口(Pow后缀)。
用户可以通过import std.overflow.*引入全部接口,也可以像标准库一样,用同样的方式为自己的类型实现这些接口(参见 overflow_package_overview.md)。
二、五种接口族的方法清单与统一语义
五类接口在方法集合上保持了一致性,均覆盖加、减、乘、除、取余、自增、自减、取负、左移、右移共十类运算;其中Int64与部分类型额外实现了幂运算(Pow)。
2.1 CarryingOp<T>:同时返回"是否截断"与结果
public interface CarryingOp<T> { func carryingAdd(y: T): (Bool, T) func carryingSub(y: T): (Bool, T) func carryingMul(y: T): (Bool, T) func carryingDiv(y: T): (Bool, T) func carryingMod(y: T): (Bool, T) func carryingInc(): (Bool, T) func carryingDec(): (Bool, T) func carryingNeg(): (Bool, T) func carryingShl(y: UInt64): (Bool, T) func carryingShr(y: UInt64): (Bool, T) }功能:提供返回整数运算是否发生了截断以及运算结果的接口。每个方法返回一个元组(Bool, T),第一个元素表示运算是否发生截断(true为截断),第二个元素是运算结果。语义上等价于"用 wrapping 策略算出结果,再告诉你有没有溢出"——从 carrying_op.cj 的源码实现可以看出,carryingAdd等方法的内部实现正是(this.checkedAdd(y).isNone(), wrappingAdd(this, y)):即先用 checked 判定是否溢出,再用 wrapping 运算给出截断后的结果。
对应的幂运算接口为CarryingPow:
public interface CarryingPow { func carryingPow(y: UInt64): (Bool, Int64) }2.2 CheckedOp<T>:溢出即返回 None
public interface CheckedOp<T> { func checkedAdd(y: T): ?T func checkedSub(y: T): ?T func checkedMul(y: T): ?T func checkedDiv(y: T): ?T func checkedMod(y: T): ?T func checkedInc(): ?T func checkedDec(): ?T func checkedNeg(): ?T func checkedShl(y: UInt64): ?T func checkedShr(y: UInt64): ?T }功能:当整数运算出现溢出,返回None。所有方法返回Option类型(?T是仓颉中Option<T>的语法糖,参见 core_package_enums.md):
- 算术类运算(Add/Sub/Mul/Div/Mod/Inc/Dec/Neg):溢出时返回
?T.None,否则返回运算结果; - 移位类运算(Shl/Shr):当移位位数大于等于操作数位数时返回
?T.None。这一点在 checked_op.cj 中有非常直接的体现,例如checkedShl(x: Int8, y: UInt64)的实现为:if (y >= 8) { None } else { Some(x << y) },Int16/32/64分别以16/32/64为阈值;IntNative/UIntNative则通过nativeBitWidth()在 32 位与 64 位之间动态判断。
幂运算接口CheckedPow仅由Int64实现,溢出时返回?Int64.None:
public interface CheckedPow { func checkedPow(y: UInt64): ?Int64 }2.3 SaturatingOp<T>:溢出时钳制到边界值
public interface SaturatingOp<T> { func saturatingAdd(y: T): T func saturatingSub(y: T): T func saturatingMul(y: T): T func saturatingDiv(y: T): T func saturatingMod(y: T): T func saturatingInc(): T func saturatingDec(): T func saturatingNeg(): T func saturatingShl(y: UInt64): T func saturatingShr(y: UInt64): T }功能:当整数运算出现溢出,做饱和处理。各方法的上溢/下溢语义略有差异:
saturatingAdd/saturatingMul/saturatingNeg:上溢返回类型最大值(MAX),下溢返回类型最小值(MIN);saturatingInc:上溢返回 MAX;saturatingDec:下溢返回 MIN;saturatingDiv/saturatingMod:上溢返回 MAX;saturatingShl/saturatingShr:当移位位数大于等于操作数位数时,将移位位数置为"操作数位数 - 1"后再运算,而不是直接钳制结果。
对应的幂运算接口为SaturatingPow(仅Int64),同样采用"上溢返回 MAX、下溢返回 MIN"的语义:
public interface SaturatingPow { func saturatingPow(y: UInt64): Int64 }2.4 ThrowingOp<T>:溢出即抛异常
public interface ThrowingOp<T> { func throwingAdd(y: T): T func throwingSub(y: T): T func throwingMul(y: T): T func throwingDiv(y: T): T func throwingMod(y: T): T func throwingInc(): T func throwingDec(): T func throwingNeg(): T func throwingShl(y: UInt64): T func throwingShr(y: UInt64): T }功能:当整数运算出现溢出,抛出异常。它把"检测 + 处理"的责任交给调用方的try/catch:
- 算术类运算溢出时抛出 OverflowException;
- 移位类运算(
throwingShl/throwingShr)当移位位数大于等于操作数位数时抛出 OvershiftException。
幂运算接口ThrowingPow由Int64实现,溢出时抛出OverflowException:
public interface ThrowingPow { func throwingPow(y: UInt64): Int64 }2.5 WrappingOp<T>:溢出即高位截断
public interface WrappingOp<T> { func wrappingAdd(y: T): T func wrappingSub(y: T): T func wrappingMul(y: T): T func wrappingDiv(y: T): T func wrappingMod(y: T): T func wrappingInc(): T func wrappingDec(): T func wrappingNeg(): T func wrappingShl(y: UInt64): T func wrappingShr(y: UInt64): T }功能:当整数运算出现溢出,将运算结果中超出目标类型位数的高位截断。这是最接近底层硬件行为的策略——结果相当于对2^位宽取模。
移位运算的规则值得特别注意:当移位位数大于等于操作数位数时,仅取 y 的低若干位作为实际移位位数。例如对Int8移位,当 y ≥ 8 时只取 y 的低 3 位,保证移位位数在 0~7 之间。这在 wrapping_op.cj 中体现为:
@Frozen public func wrappingShl(y: UInt64): Int8 { if (y >= 8) { return this << (y & 0b111) } else { return this << y } }对应的幂运算接口为WrappingPow(仅Int64):
public interface WrappingPow { func wrappingPow(y: UInt64): Int64 }三、接口实现矩阵:覆盖全部 10 种整数类型
标准库通过extend扩展,为Int8/Int16/Int32/Int64/IntNative与UInt8/UInt16/UInt32/UInt64/UIntNative共 10 种整数类型实现了上述接口。实现方式分为两类:
- 纯扩展实现:如
extend Int16 <: CarryingOp<Int16>、extend UInt32 <: CheckedOp<UInt32>等,直接为类型补上接口方法; - 组合实现:仅
Int64采用组合方式,如extend Int64 <: CarryingOp<Int64> & CarryingPow、extend Int64 <: CheckedOp<Int64> & CheckedPow、extend Int64 <: SaturatingOp<Int64> & SaturatingPow、extend Int64 <: ThrowingOp<Int64> & ThrowingPow、extend Int64 <: WrappingOp<Int64> & WrappingPow——即Int64是唯一同时实现 Pow 幂运算接口的类型(因为Int64是仓颉中与原生字长对应的、幂运算最有实际意义的 64 位有符号整数)。
各接口在 overflow_package_interfaces.md 中的实现分布如下:
| 接口 | 实现类型(扩展声明) | 文档位置 |
|---|---|---|
| CarryingOp<T> | Int16 / Int32 / Int64(+CarryingPow) / Int8 / IntNative / UInt16 / UInt32 / UInt64 / UInt8 / UIntNative | 第 3~4765 行区间 |
| CheckedOp<T> | Int16 / Int32 / Int64(+CheckedPow) / Int8 / IntNative / UInt16 / UInt32 / UInt64 / UInt8 / UIntNative | 第 4794~10505 行区间 |
| SaturatingOp<T> | Int16 / Int32 / Int64(+SaturatingPow) / Int8 / IntNative / UInt16 / UInt32 / UInt64 / UInt8 / UIntNative | 第 10534~16539 行区间 |
| ThrowingOp<T> | Int16 / Int32 / Int64(+ThrowingPow) / Int8 / IntNative / UInt16 / UInt32 / UInt64 / UInt8 / UIntNative | 第 16568~21916 行区间 |
| WrappingOp<T> | Int16 / Int32 / Int64(+WrappingPow) / Int8 / IntNative / UInt16 / UInt32 / UInt64 / UInt8 / UIntNative | 第 21949~27030 行区间 |
从源码角度看,各策略文件一一对应:carrying_op.cj、checked_op.cj、saturating_op.cj、throwing_op.cj、wrapping_op.cj。其中 wrapping 系列底层运算(wrappingAdd、wrappingSub、wrappingMul、wrappingDiv、wrappingMod、wrappingPow、wrappingInc、wrappingDec、wrappingNeg)全部以@Intrinsic标注,由编译器直接内联为无溢出检查的机器指令运算,这也解释了为何 wrapping 是性能开销最低的策略。
四、实战示例:五种策略的调用与运行结果
以下示例全部可在安装了仓颉工具链的环境中直接编译运行,示例来自 overflow_samples 目录以及接口文档中标记为<!-- verify -->的验证用例。
4.1 高位截断(wrapping):UInt8.Max + 1 = 0
UInt8.Max的二进制表示为0b11111111,加 1 后为0b100000000(9 位),由于UInt8只能储存 8 位,高位截断后结果为 0。参见 wrapping.md:
import std.overflow.* main() { // 尝试计算 UInt8.Max + 1,发生上溢时高位截断 let value: UInt8 = UInt8.Max println("wrappingAdd 计算结果: ${value.wrappingAdd(1)}") }运行结果:
wrappingAdd 计算结果: 04.2 饱和(saturating):UInt16.Max 乘 16 = 65535
UInt16.Max乘 16 的结果远超UInt16上限,发生上溢时饱和为UInt16.Max。参见 saturating.md:
import std.overflow.* main() { // 尝试计算 UInt16.Max 乘 16,发生上溢时饱和为 UInt16 的最大值 let value: UInt16 = UInt16.Max println("saturatingMul 计算结果: ${value.saturatingMul(16)}") }运行结果:
saturatingMul 计算结果: 655354.3 返回 Option(checked):Int64.Max 的平方返回 None
Int64.Max的平方超出Int64范围,checkedPow返回None。参见 option.md:
import std.overflow.* main() { // 尝试计算 Int64.Max 的平方,发生溢出时返回 None let value: Int64 = Int64.Max println("checkedPow 计算结果: ${value.checkedPow(UInt64(2))}") }运行结果:
checkedPow 计算结果: None4.4 抛出异常(throwing):Int64.Max + 1 抛出 OverflowException
Int64.Max + 1发生溢出,抛出OverflowException。参见 throwing.md:
import std.overflow.* main() { // 尝试计算 Int64.Max + 1,发生溢出时抛出 OverflowException let value: Int64 = Int64.Max println(value.throwingAdd(1)) }运行结果:
An exception has occurred: OverflowException: add4.5 同时返回截断标志与结果(carrying):Int16 溢出示例
CarryingOp的特点是"结果照算(wrapping 语义),同时告诉你是否截断"。接口文档给出Int16的完整示例:30000i16 + 10000i16超出Int16上限(32767),发生上溢。
import std.overflow.* main(): Int64 { // 创建两个Int16值 let a = 30000i16 let b = 10000i16 // 使用carryingAdd进行加法运算 let (overflow, result) = a.carryingAdd(b) println("Carrying add result: ${result}") println("Overflow occurred: ${overflow}") return 0 }运行结果:
Carrying add result: -25536 Overflow occurred: true接口文档中还提供了其余 9 种运算(carryingSub、carryingMul、carryingDiv、carryingMod、carryingInc、carryingDec、carryingNeg、carryingShl、carryingShr)在Int16、Int32、Int64、Int8及全部无符号类型上的验证用例,这里列举几个边界值场景供对照:
| 运算 | 输入 | 结果 | 是否截断 |
|---|---|---|---|
| carryingInc | Int8.Max(127) | -128 | true |
| carryingDec | Int16.Min(-32768) | 32767 | true |
| carryingNeg | Int32.Min(-2147483648) | -2147483648 | true |
| carryingDiv | Int64.Min / -1 | Int64.Min | true |
| carryingMod | Int64.Min % -1 | 0 | true |
| carryingShl | Int16(10000) << 5 | -7680 | false |
| carryingPow | Int64(2) 的 64 次幂 | 0 | true |
可以看出:移位运算只要位数在类型位宽范围内就不会报告截断(如Int16左移 5 位仍属于正常移位),而Int64.Min / -1、Int64.Min % -1这类经典的"结果超出正数范围"的除法/取余场景,以及MIN的取负、MAX的自增、MIN的自减,都会触发截断标志。
五、移位运算与异常类的专项说明
移位策略是五种接口中最容易出现"反直觉"行为的部分,这里集中对比一下:
| 接口族 | 移位位数 y ≥ 操作数位宽时的行为 |
|---|---|
| checkedShl / checkedShr | 返回None |
| saturatingShl / saturatingShr | 将 y 置为"位宽 - 1"后正常移位 |
| throwingShl / throwingShr | 抛出 OvershiftException |
| wrappingShl / wrappingShr | 仅取 y 的低 log2(位宽) 位作为实际移位位数 |
overflow 包额外定义了两种移位异常类(详见 overflow_package_exceptions.md):
- OvershiftException:移位位数超过操作数位数时抛出,提供无参构造
init()与带消息构造init(message: String); - UndershiftException:移位位数小于 0 时抛出,同样提供两种构造。
两者都继承自 Exception,可通过catch (e: OvershiftException)捕获。示例:
import std.overflow.* main(): Int64 { try { // 抛出带消息的OvershiftException异常 throw OvershiftException("Shift count exceeds the number of bits in the operand") } catch (e: OvershiftException) { println("Caught OvershiftException: ${e}") println("Exception message: ${e.message}") } return 0 }运行结果:
Caught OvershiftException: OvershiftException: Shift count exceeds the number of bits in the operand Exception message: Shift count exceeds the number of bits in the operand值得注意的是,从接口文档看UndershiftException针对的是"移位位数小于 0"的场景;而由于各接口的移位方法签名统一为y: UInt64(无符号),实际调用时传入负数会先发生类型转换,因此 throwing 系列移位方法真正会抛出的主要是OvershiftException。
六、源码级原理:从接口到 @Intrinsic
如果想深入理解这套 API 的底层实现,推荐直接阅读 stdlib/libs/std/overflow/ 目录下的 6 个源文件,它们与接口文档一一对应:
| 源文件 | 对应接口 |
|---|---|
| carrying_op.cj | CarryingOp / CarryingPow |
| checked_op.cj | CheckedOp / CheckedPow |
| saturating_op.cj | SaturatingOp / SaturatingPow |
| throwing_op.cj | ThrowingOp / ThrowingPow |
| wrapping_op.cj | WrappingOp / WrappingPow |
| checked_op_cjnative.cj | CheckedOp 的 CJNative 后端实现 |
几个值得注意的实现细节:
- Carrying 是 Checked 与 Wrapping 的组合:
carrying_op.cj中所有方法都是(this.checkedXxx(y).isNone(), wrappingXxx(this, y))的形式——第一元素复用 checked 的溢出检测,第二元素复用 wrapping 的截断结果,实现"既报告又返回"的语义; - Wrapping 底层是编译器内建指令:
wrapping_op.cj中wrappingAdd/Sub/Mul/Div/Mod/Pow/Inc/Dec/Neg均声明为@Intrinsic泛型函数,编译器会直接生成对应的原生整数运算指令,不做任何溢出检查,因此这是性能最优的策略; - Checked 的移位检测按位宽分派:
checked_op.cj为Int8/16/32/64、UInt8/16/32/64各写了一套checkedShl/checkedShr重载,阈值分别取 8/16/32/64;IntNative/UIntNative则通过nativeBitWidth()(运行时检测平台字长为 32 还是 64)动态决定阈值; - Native 类型的平台自适应:
checked_op.cj顶部用UInt64(!UIntNative(0))与UInt64.Max/UInt64(UInt32.Max)比较,判断当前平台IntNative是 64 位还是 32 位,从而保证 Native 类型的移位阈值、掩码在不同平台上行为一致。
七、如何选择策略
综合以上分析,可以给出策略选型建议:
- 需要感知溢出并自行处理→ 使用
CheckedOp,通过Option判断,或使用CarryingOp在拿到结果的同时拿到截断标志; - 需要结果永远落在类型范围内(如 UI 进度条、采样计数器)→ 使用
SaturatingOp,天然钳制在 MIN/MAX 之间; - 溢出属于不可恢复的错误→ 使用
ThrowingOp(或直接依赖语言的默认溢出抛异常行为),配合try/catch处理; - 追求性能、明确接受截断语义(哈希、校验和、位运算领域)→ 使用
WrappingOp,底层由@Intrinsic编译为无检查的原生运算。
std.overflow包全部 API 的完整签名、参数说明与验证示例,可在 overflow_package_interfaces.md 与 overflow_package_exceptions.md 中逐一查阅;四种策略的入门示例见 overflow_samples;包的总体介绍与接口速查表见 overflow_package_overview.md。
- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
相关推荐
仓颉编程语言 std.overflow 溢出处理接口全解析:五大策略与十种整数类型实战
仓颉编程语言 std.overflow 溢出处理接口全解析:五大策略与十种整数类型实战 本文以仓颉标准库 std.overflow 包为主线,系统梳理 Carr
文档知识库教程OpenHarmony仓颉编程语言 std.overflow 包指南:整数溢出四种处理策略与接口体系详解
仓颉编程语言 std.overflow 包指南:整数溢出四种处理策略与接口体系详解 本文围绕仓颉标准库 std.overflow 包展开,系统讲解整数运算溢出的
文档知识库教程OpenHarmony仓颉 std.overflow 包完全指南:整数溢出的四种处理策略与源码实现剖析
仓颉 std.overflow 包完全指南:整数溢出的四种处理策略与源码实现剖析 导读 在仓颉(Cangjie)语言中,当整数运算结果超出其类型的最大值或小于最
编程语言语言运行时标准库垃圾回收并发编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考