第12章 数值类
理解Numeric层次、字面量、算术、Math、转换、位运算、随机与计数,并用明确representation和rounding policy管理近似误差。
学习目标
- 能解释数值类的构成与数值的字面量在“第12章 数值类”中的责任边界
- 能围绕“怎样为整数、浮点、随机数与近似比较选择正确数值域和边界?”运行正常与故障轨迹并定位首个分岔
- 能用“第12章 数值类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“每个中间值的类型、范围、舍入策略和随机种子都明确。”
来源、版次与运行边界
“第12章 数值类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第12章 数值类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第12章 数值类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样为整数、浮点、随机数与近似比较选择正确数值域和边界?”,本页验收“第12章 数值类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“先执行整数除法再转为浮点,错误的小数部分已经无法恢复”;若无法定位第一处状态分岔,就拒绝当前解释。
从“数字都是number”这个抽象泄漏开始
Ruby用Numeric层次统一许多operator protocol,但Integer、Float、Rational与Complex表达不同domain。先于选择method。
先预测:5 / 2为何不是2.5?两个Integer执行integer division;改成5.fdiv(2)或让一个operand为Float才得到2.5。Operator surface相同,不代表result domain相同。
数值类的构成
Numeric是抽象superclass,常见subclasses包括Integer、Float、Rational、Complex。Ruby 2.3已统一Fixnum/Bignum行为但class names仍可能可见,现代Ruby合并为Integer;业务代码不要依赖数值大小触发的内部class切换。
Integer提供任意精度语义,memory/time随位数增长;它不会overflow成负数,却可能被恶意超长输入耗尽资源。Float通常基于IEEE 754 double,包含Infinity与NaN等边界。Rational精确保存整数比,Complex保存实部/虚部。
数值的字面量
Integer literal可用十进制、0x十六进制、0b二进制、0o八进制与下划线分组;Float可用decimal point和exponent。不同于运行时String parsing。
p 1_000_000
p 0xff
p 0b1010
p 6.02e23
p Rational(1, 3)
p Complex(2, 3)External text不要用to_i默认吞掉invalid suffix和返回0;使用Integer(text, 10)、Float(text)并处理ArgumentError,再做length/range/domain validation。Leading zero与base自动识别可能引起歧义,协议中固定base。
算术运算
+ - * / % **调用receiver operator methods。Integer /是floor-style integer quotient,div明确整数除法,fdiv明确浮点除法;%/modulo与remainder在负数符号上可不同。避免跨语言误迁移。
p 5 / 2
p 5.fdiv(2)
p(-5.div(2))
p(-5 % 2)
p(-5.remainder(2))ZeroDivisionError对Integer除零抛出;Float除零可能得到Infinity/NaN,取决于operation。检测finite?、nan?并在domain boundary拒绝无效结果。Exponent可能生成巨大Integer或overflow Float,输入指数必须限制。
Math模块
Math提供sqrt、sin、cos、log等module functions,通常返回Float。输入domain错误会抛Math::DomainError或产生特殊值,目标版本需验证。Angle单位通常是radian;API边界明确degree conversion。
Math函数的结果精度受Float限制。Scientific code应记录algorithm、tolerance、conditioning与library/runtime version;业务display rounding不应反向用于中间计算。
数值类型转换
Integer(value)与Float(value)执行较严格转换,to_i/to_f往往更宽松。不应混成一个truthy check。
Float转Integer的to_i向0截断;floor向负无穷,ceil向正无穷,round按版本/参数规则取整。金额、分页和坐标各有不同rounding policy,方法名或value object应体现,不要散落.to_i。
跨JSON/CSV/database时记录decimal scale、large Integer范围和NaN/Infinity是否可表示。Round-trip test比只测一次parse更能发现precision loss。
位运算
Integer支持& | ^ ~ << >>。Ruby Integer不是固定32/64-bit register,负数位运算可按无限two's-complement直觉表现;与C protocol交互时显式mask width。把抽象Integer映射到协议。
READ = 0b001
WRITE = 0b010
EXECUTE = 0b100
permissions = READ | WRITE
p((permissions & WRITE) != 0)
p permissions ^ WRITE
p((~permissions) & 0b111)Shift count来自输入时限制范围;left shift可产生巨大Integer。Bit flag适合稳定紧凑protocol,但业务可读性需要named constants与encode/decode tests。
随机数
rand使用默认generator;Random.new(seed)创建独立可复现sequence。区分simulation/test与security。
rng = Random.new(12_345)
samples = 5.times.map { rng.rand(1..6) }
p samples测试注入seeded Random,不修改process-global seed影响其它tests。Token、nonce、password reset使用SecureRandom等CSPRNG,不使用普通rand。统计需求还要验证distribution,不以五个样本证明均匀。
计数
Integer#times、upto、downto和step表达不同sequence;无block时返回Enumerator。避免off-by-one。
1.upto(3)产生1,2,3;3.downto(1)产生3,2,1;1.step(10, 3)产生1,4,7,10。Float step会累积误差,优先按Integer index计算目标值或使用Rational/Decimal domain。
近似值误差
Binary Float无法精确表示多数decimal fraction,因此0.1 + 0.2 == 0.3通常false。不能统一用round掩盖。
actual = 0.1 + 0.2
expected = 0.3
tolerance = 1e-12
p actual
p((actual - expected).abs <= tolerance)Tolerance应结合magnitude使用absolute+relative标准;near zero与大值策略不同。Money使用Integer最小单位或BigDecimal并固定rounding;geometry/physics记录单位和error accumulation。Round只在display、settlement或协议指定boundary执行,避免每步round扩大偏差。
正式节点与章专属证据
- ↡在“第12章 数值类”中,数值类的构成必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,数值的字面量必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,算术运算必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,Math模块必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,数值类型转换必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,位运算必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,随机数必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,计数必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
- ↡在“第12章 数值类”中,近似值误差必须连接输入、状态变化与可复核结果。 :第 9 个正式节点要能回到“每个中间值的类型、范围、舍入策略和随机种子都明确。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样为整数、浮点、随机数与近似比较选择正确数值域和边界?
输入与接收者
固定数值类的构成所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明数值的字面量的允许状态。
观察证据
保存第12章 数值类的初值、参数、编码或资源位置。
正式节点:数值类的构成、数值的字面量、算术运算、Math模块、数值类型转换、位运算、随机数、计数、近似值误差
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定数值类的构成的输入和接收者
- 02执行数值的字面量并记录状态
- 03观察算术运算的返回或副作用
- 04用近似值误差核对不变量并复位
运行不变量:每个中间值的类型、范围、舍入策略和随机种子都明确。
边界故障探针
一次只破坏一个前提
本章回顾:Representation决定可证明的数值语义
- Numeric subclasses覆盖不同domain;Integer arbitrary precision不等于无资源上界,Float不提供decimal exactness。
- Literal grammar与runtime parsing不同,外部输入按parse、convert、validate三阶段处理。
- Division、remainder、Math和bit operations要声明operand type、sign、width与error。
- Randomness声明seed/distribution/security,counting声明endpoint/step,便于复现和避免off-by-one。
- Approximate comparison依据domain使用tolerance或exact Decimal/Rational,rounding只发生在命名边界。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样为整数、浮点、随机数与近似比较选择正确数值域和边界?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“先执行整数除法再转为浮点,错误的小数部分已经无法恢复”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联数值类的构成、数值的字面量、算术运算、Math模块、数值类型转换、位运算、随机数、计数、近似值误差,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 数值类的构成
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 数值的字面量
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 算术运算
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- Math模块
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 数值类型转换
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 位运算
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 随机数
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 计数
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 近似值误差
“第12章 数值类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。