☰
仓颉标准库 std.overflow 溢出处理接口完全指南:五种策略与全部整数类型的 API 详解
2026/10/15 10:35:12 网站建设 项目流程
  • 编程语言
  • 语言运行时
  • 标准库
  • 垃圾回收
  • 并发编程

【免费下载链接】cangjie_runtime

仓颉编程语言运行时与标准库。

项目地址:https://gitcode.com/Cangjie/cangjie_runtime
点击查看免费下载

本文是仓颉(Cangjie)编程语言标准库std.overflow包接口文档的技术导读,完整梳理了CheckedOp、SaturatingOp、ThrowingOp、WrappingOp、CarryingOp五类溢出处理接口及其幂运算扩展接口(CheckedPow、SaturatingPow、ThrowingPow、WrappingPow、CarryingPow)的签名、行为语义与使用方式。读完本文,你将掌握仓颉中整数溢出的五种处理策略、如何在Int8到UInt64的全部整数类型上调用这些 API,并能结合实际示例理解溢出场景下的返回值与异常行为。

一、overflow 包概述:为什么需要显式的溢出策略

在仓颉语言中,整数运算一旦出现溢出(运算结果大于类型最大值或小于类型最小值),默认行为是抛出异常。这在绝大多数场景下是安全的选择,但部分领域(如哈希计算、循环冗余校验、位图算法、密码学实现、协议字段编解码)需要显式控制溢出行为:要么检测、要么忽略、要么钳制到边界值。

std.overflow包正是为此而生。它定义了四种溢出处理策略,并通过接口 + 类型扩展的方式为所有整数类型统一提供了对应的方法:

策略接口描述
返回 OptionCheckedOp当整数运算出现溢出,返回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 种整数类型实现了上述接口。实现方式分为两类:

  1. 纯扩展实现:如extend Int16 <: CarryingOp<Int16>、extend UInt32 <: CheckedOp<UInt32>等,直接为类型补上接口方法;
  2. 组合实现:仅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 计算结果: 0

4.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 计算结果: 65535

4.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 计算结果: None

4.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: add

4.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及全部无符号类型上的验证用例,这里列举几个边界值场景供对照:

运算输入结果是否截断
carryingIncInt8.Max(127)-128true
carryingDecInt16.Min(-32768)32767true
carryingNegInt32.Min(-2147483648)-2147483648true
carryingDivInt64.Min / -1Int64.Mintrue
carryingModInt64.Min % -10true
carryingShlInt16(10000) << 5-7680false
carryingPowInt64(2) 的 64 次幂0true

可以看出:移位运算只要位数在类型位宽范围内就不会报告截断(如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.cjCarryingOp / CarryingPow
checked_op.cjCheckedOp / CheckedPow
saturating_op.cjSaturatingOp / SaturatingPow
throwing_op.cjThrowingOp / ThrowingPow
wrapping_op.cjWrappingOp / WrappingPow
checked_op_cjnative.cjCheckedOp 的 CJNative 后端实现

几个值得注意的实现细节:

  1. Carrying 是 Checked 与 Wrapping 的组合:carrying_op.cj中所有方法都是(this.checkedXxx(y).isNone(), wrappingXxx(this, y))的形式——第一元素复用 checked 的溢出检测,第二元素复用 wrapping 的截断结果,实现"既报告又返回"的语义;
  2. Wrapping 底层是编译器内建指令:wrapping_op.cj中wrappingAdd/Sub/Mul/Div/Mod/Pow/Inc/Dec/Neg均声明为@Intrinsic泛型函数,编译器会直接生成对应的原生整数运算指令,不做任何溢出检查,因此这是性能最优的策略;
  3. Checked 的移位检测按位宽分派:checked_op.cj为Int8/16/32/64、UInt8/16/32/64各写了一套checkedShl/checkedShr重载,阈值分别取 8/16/32/64;IntNative/UIntNative则通过nativeBitWidth()(运行时检测平台字长为 32 还是 64)动态决定阈值;
  4. 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

仓颉编程语言运行时与标准库。

项目地址:https://gitcode.com/Cangjie/cangjie_runtime
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询