ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

100-exercises-to-learn-rust:逐操作选择整数溢出行为——`wrapping_` 与 `saturating_` 方法实战

100-exercises-to-learn-rust:逐操作选择整数溢出行为——`wrapping_` 与 `saturating_` 方法实战 示例工程教程【免费下载链接】100-exercises-to-learn-rustA self-paced course to learn Rust, one exercise at a time.项目地址https://gitcode.com/GitHub_Trending/10/100-exercises-to-learn-rust点击查看免费下载导读在 Rust 中整数溢出overflow与下溢underflow的处理方式不是一刀切的overflow-checks这个 Cargo profile 全局开关过于钝无法让同一个程序在不同上下文里分别选择 panic 或环绕wrap语义。本文基于 100-exercises-to-learn-rust 教程的 book/src/02_basic_calculator/09_saturating.md 一节系统讲解wrapping_*与saturating_*系列方法并结合仓库中的saturating练习exercises/02_basic_calculator/09_saturating/src/lib.rs演示如何把饱和算术落地到真实的阶乘计算中。读完你将掌握在哪些场景应选择环绕、饱和还是 panic以及如何用逐操作per-operation的方法精确控制每种算术结果。为什么需要逐操作控制overflow-checks的局限性在进入本节的wrapping_/saturating_方法之前必须先理解它们要解决的问题。上一节 book/src/02_basic_calculator/08_overflow.md 已经介绍过整数运算结果超出类型的MAX时发生溢出小于MIN时发生下溢Rust 不会自动把结果提升到更大的整数类型no automatic promotion应对策略只有两条路拒绝运算panic或给出一个能装进该类型的结果环绕 wrap。全局行为由 Cargo profile 的overflow-checks设置控制devprofile 默认overflow-checks true调试时溢出即 panic尽早暴露潜在问题releaseprofile 默认overflow-checks false追求运行时性能溢出时静默环绕。本节的文档把overflow-checks形容为一把钝器a blunt tool它是一个影响整个程序的全局开关。但在真实代码里我们经常需要按上下文区别对待溢出有时环绕是正确选择——例如哈希、校验和、计数器回卷等场景本来就要利用模运算语义有时panic更可取——例如计算费用、索引、坐标等业务数据静默出错会造成严重事故。一个全局开关显然无法同时满足这两种需求。于是 Rust 提供了按单个运算粒度显式选择的 APIwrapping_*方法和saturating_*方法。wrapping_方法按运算选择环绕语义当你想在某一次特定运算中启用环绕行为时可以使用wrapping_系列方法而不必改动全局 profile。文档给出的加法示例let x 255u8; let y 1u8; let sum x.wrapping_add(y); assert_eq!(sum, 0);255u8 1u8的数学结果是 256超出了u8::MAX255因此环绕回u8::MIN0。这与 book/src/02_basic_calculator/08_overflow.md 中描述的把整数类型的所有取值看成一个圆环到达最大值后从最小值重新开始完全一致——对带符号类型同样适用例如127i8.wrapping_add(1)会得到i8::MIN-128。wrapping_系列并非只有加法。标准库为所有整数类型都提供了成体系的环绕运算方法常用的包括wrapping_add/wrapping_sub/wrapping_mulwrapping_div/wrapping_remwrapping_neg/wrapping_abswrapping_powwrapping_shl/wrapping_shr需要留意环绕除法有特殊规则——x.wrapping_div(0)和MIN.wrapping_div(-1)依然会 panic因为它们连环绕出一个结果的定义都没有。saturating_方法饱和到类型的最大/最小值与环绕不同饱和算术saturating arithmetic不会绕回起点而是把结果钳制clamp在整数类型的最小值与最大值之间上溢时返回MAX下溢时返回MIN。文档示例let x 255u8; let y 1u8; let sum x.saturating_add(y); assert_eq!(sum, 255);因为255 1 256 u8::MAX结果被饱和为u8::MAX255。对称地下溢场景0u8.saturating_sub(1)的数学结果是 -1小于u8::MIN0因此结果饱和为u8::MIN0。常用saturating_方法包括saturating_add/saturating_sub/saturating_mulsaturating_abs/saturating_negsaturating_pow一个非常关键且容易被忽略的事实文档特意强调饱和算术无法通过overflow-checksprofile 设置获得。overflow-checks只有panic和wrap两种语义见 book/src/02_basic_calculator/08_overflow.md 的overflow-check小节不存在饱和模式。你必须在执行算术运算的那一刻显式调用saturating_方法才能拿到饱和语义。实战在阶乘练习中应用饱和乘法理解了方法本身接下来看本仓库如何把它用在一个真实的练习上。在 exercises/02_basic_calculator/09_saturating/src/lib.rs 中练习目标是实现一个factorial(n: u32) - u32函数。模板代码是这样给出的pub fn factorial(n: u32) - u32 { let mut result 1; for i in 1..n { // Use saturating multiplication to stop at the maximum value of u32 // rather than overflowing and wrapping around result * i; } result }注释已经点明了改造方向把result * i换成饱和乘法。对照同目录测试同一文件内的tests模块#[test] fn twentieth() { assert_eq!(factorial(20), u32::MAX); }为什么factorial(20)必须等于u32::MAX因为20! 2,432,902,008,176,640,000早已超过u32::MAX4,294,967,295——事实上上一节的 book/src/02_basic_calculator/08_overflow.md 就指出20!甚至超过了 32 位有符号整数的最大值 2,147,483,647。因此在累乘过程中一旦乘积即将超出范围饱和乘法就会把它钉在u32::MAX上后续乘法继续饱和最终结果恰好是u32::MAX。正确解法是把乘法显式改为result result.saturating_mul(i);修改后cargo test会通过全部测试用例factorial(0)1、factorial(1)1、factorial(2)2、factorial(5)120、factorial(20)u32::MAX。值得一提的对比紧邻的上一节练习 exercises/02_basic_calculator/08_overflow/src/lib.rs 是另一个方向的答案——它要求把仓库根目录 Cargo.toml 中的devprofile 配置为溢出时环绕于是factorial(20)的测试期望值变成了环绕结果2_192_834_560。这两个练习放在一起恰好演示了同一道阶乘题在全局环绕与逐操作饱和两种策略下的差异前者借助 profile 全局生效后者则通过方法调用在单个运算上显式选择语义。// 对比08_overflow 练习全局环绕 assert_eq!(factorial(20), 2_192_834_560); // 环绕后的结果 // 对比09_saturating 练习逐操作饱和 assert_eq!(factorial(20), u32::MAX); // 饱和后的结果仓库中的全局 profile 佐证为什么逐操作控制有意义在仓库根目录的 Cargo.toml 里有一行很能说明问题的配置[profile.dev.package.copy] overflow-checks true注释写明这一配置是为了保证某个特定练习的预期行为而不受devprofile 上overflow-checks全局设置的影响。也就是说即使工作区全局的devprofile 溢出行为被调整过copy这个包仍可单独强制开启溢出检查。这从侧面印证了文档的核心论点溢出策略本质上是粒度问题——从全局 profile到包级覆盖再到单个运算上的wrapping_*/saturating_*方法Rust 提供了逐层细化的控制手段。本文讨论的saturating_*就是其中最细的一层。如何选择什么时候用哪种策略综合 book/src/02_basic_calculator/09_saturating.md、book/src/02_basic_calculator/08_overflow.md 以及 book/src/02_basic_calculator/04_panics.md 的内容可以把决策思路归纳如下场景推荐策略手段调试阶段想尽早暴露逻辑缺陷panic默认devprofileoverflow-checks true哈希、校验和、计数器等模运算语义环绕wrapping_*方法或releaseprofileoverflow-checks false数值一旦越界就封顶即可如限速、配额、进度条饱和saturating_*方法全局统一启用溢出检查宁可崩溃也不要静默出错panic在 Cargo.toml 中为各 profile 显式设置overflow-checks true文档对全局开关的态度也很明确建议为两个 profile 都开启overflow-checks宁可崩溃也不要静默产生错误结果运行时性能损失在绝大多数场景可以忽略只有在性能敏感的代码里才需要跑基准测试来权衡。但无论 profile 怎么配saturating_*都是唯一能按单个运算拿到饱和语义的途径——这正是本节存在的意义。关于方法的补充说明文档脚注提醒wrapping_add、saturating_add这类写法中的.语法调用的是方法method——可以理解为附着在某个具体类型上的函数。以255u8.saturating_add(1)为例方法通过点语法直接作用于u8值上。在本教程中方法以及如何自定义方法是下一章 book/src/03_ticket_v1/01_struct.md 才会正式展开的主题但在这里你只需知道255u8.wrapping_add(1)与u8::wrapping_add(255, 1)本质上是同一件事的两种写法前者是更常见的惯用形式。小结overflow-checks是全局钝器只有 panic / wrap 两种语义无法表达饱和wrapping_*方法让环绕成为逐操作选择255u8.wrapping_add(1) 0u8saturating_*方法让饱和成为逐操作选择255u8.saturating_add(1) 255u8下溢同理钳制到MIN饱和语义只能靠显式调用方法获得任何 profile 设置都替代不了仓库中的saturating练习exercises/02_basic_calculator/09_saturating/用saturating_mul实现了阶乘封顶在u32::MAX的行为与08_overflow练习的环绕版形成直观对比是本节的理想练手素材。赞分享示例工程教程【免费下载链接】100-exercises-to-learn-rustA self-paced course to learn Rust, one exercise at a time.项目地址https://gitcode.com/GitHub_Trending/10/100-exercises-to-learn-rust点击查看免费下载相关推荐Redwood 应用部署到 AWSFlightcontrol 部署实战指南Redwood 应用部署到 AWSFlightcontrol 部署实战指南 本文以 Redwood v1.x 官方文档的 Flightcontrol 部署指南示例工程教程GitHub_Trending/10/100-exercises-to-learn-rust外部函数接口Rust与C语言的互操作实践GitHub_Trending/10/100 exercises to learn rust外部函数接口Rust与C语言的互操作实践 你是否在开发中遇到需要调示例工程教程Linux Housekeeping 机制全解析CPU 隔离、RCU 同步与 cpumask 管理Linux Housekeeping 机制全解析CPU 隔离、RCU 同步与 cpumask 管理 Housekeeping 是 Linux 内核 CPU 隔示例工程教程上一篇Turborepo 依赖管理最佳实践在 monorepo 中正确安装、同步与锁定依赖下一篇RIOT 操作系统 AT24CXXX I2C EEPROM 驱动测试指南从烧录运行到源码级原理解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表