本课目标
完成本课后,你将能够:
- 深入理解 IEEE 754 浮点数的表示与精度限制
- 掌握 Ada 中浮点类型的定义、属性与使用
- 熟练使用定点类型实现精确小数运算
- 理解
digits与delta的物理意义及选择策略 - 应用数值稳定性分析方法编写高可靠数值计算代码
一、浮点类型基础
1.1 IEEE 754 浮点表示
浮点数在计算机中采用科学记数法存储,由符号、指数和尾数三部分组成:
| 精度 | 总位数 | 符号位 | 指数位 | 尾数位 | 十进制精度 |
|---|---|---|---|---|---|
| 单精度 | 32 | 1 | 8 | 23 | 约 6~7 位 |
| 双精度 | 64 | 1 | 11 | 52 | 约 15~16 位 |
| 扩展精度 | 80 | 1 | 15 | 64 | 约 18~19 位 |
重要特性:
- 浮点运算是近似的,多数十进制小数无法精确表示(如 0.1)
- 存在舍入误差、下溢、上溢和特殊值(NaN、无穷大)
1.2 Ada 中的预定义浮点类型
Ada 提供了三个标准的浮点类型族:
type Float is digits implementation_defined; -- 通常单精度
type Long_Float is digits implementation_defined; -- 通常双精度
type Long_Long_Float is digits implementation_defined; -- 扩展精度
实际精度由编译器决定,可通过属性查询:
with Ada.Text_IO;
procedure Float_Info is
begin
Ada.Text_IO.Put_Line ("Float digits: " & Integer'Image (Float'Digits));
Ada.Text_IO.Put_Line ("Float mantissa: " & Integer'Image (Float'Mantissa));
Ada.Text_IO.Put_Line ("Float machine emax: " & Integer'Image (Float'Machine_Emax));
Ada.Text_IO.Put_Line ("Float machine emin: " & Integer'Image (Float'Machine_Emin));
end Float_Info;
1.3 浮点字面量与赋值
-- 浮点字面量必须包含小数点或指数
X : Float := 3.14159;
Y : Float := 2.0E-10; -- 科学记数法
Z : Float := 16#1.FFFF#E+2; -- 基于16的浮点字面量
-- 显式类型转换(限定表达式)
A : Float := Float'(100); -- 整数字面量转浮点
二、用户定义浮点类型
2.1 指定十进制精度
使用 digits 定义具有指定十进制精度的浮点类型:
-- 至少保证6位十进制有效数字
type Real is digits 6;
-- 同时指定范围
type Probability is digits 6 range 0.0 .. 1.0;
-- 高精度类型
type Double is digits 15;
type Quad is digits 18; -- 若硬件支持
编译器会自动选择满足精度要求的最小硬件浮点格式(通常是 IEEE 单精度或双精度)。
2.2 浮点类型属性
| 属性 | 含义 | 示例 |
|---|---|---|
'Digits | 十进制精度位数 | Real'Digits = 6 |
'Mantissa | 二进制尾数位数(包括隐藏位) | Float'Mantissa |
'Machine_Emax | 最大指数 | Float'Machine_Emax |
'Machine_Emin | 最小指数 | Float'Machine_Emin |
'Epsilon | 1.0 与下一个可表示数的差值 | Float'Epsilon |
'Safe_First | 算术运算安全范围的最小值 | Float'Safe_First |
'Safe_Last | 算术运算安全范围的最大值 | Float'Safe_Last |
'Model_Emin | 模型指数最小值 | Real'Model_Emin |
'Model_Epsilon | 模型相对精度 | Real'Model_Epsilon |
2.3 浮点类型派生
type Meters is new Float; -- 派生新类型,单位语义
type Seconds is new Float;
-- 重载运算符确保单位安全
function "*" (M : Meters; T : Seconds) return Meters_Second is
begin
return Meters_Second (Float (M) * Float (T));
end "*";
三、浮点运算与精度问题
3.1 舍入误差示例
with Ada.Text_IO;
procedure Rounding_Demo is
X : Float := 0.1;
Sum : Float := 0.0;
begin
for I in 1 .. 10 loop
Sum := Sum + X;
end loop;
Ada.Text_IO.Put_Line ("10 * 0.1 = " & Float'Image (Sum));
-- 可能输出 0.999999... 而非 1.0
end Rounding_Demo;
3.2 比较浮点数
错误做法:
if A = B then -- 危险!因舍入误差很可能不相等
正确做法:使用容差比较
function Approx_Equal (A, B : Float; Eps : Float := 1.0E-6) return Boolean is
begin
return abs (A - B) <= Eps * (abs A + abs B) / 2.0; -- 相对误差
end Approx_Equal;
Ada 提供了通用函数 Ada.Numerics.Generic_Real_Arrays 等,也可用属性:
if abs (A - B) <= Float'Model_Epsilon * (abs A + abs B) then ...
3.3 避免精度丢失的技巧
- 避免加减大数和小数:可能丢失小数部分
- 使用 Kahan 求和算法:补偿舍入误差
- 选择合适的数据类型:高精度类型如
Long_Long_Float - 避免对非常接近的数相减(灾难性抵消)
-- 灾难性抵消示例
A := 1.2345678;
B := 1.2345679;
C := B - A; -- 有效数字严重丢失
3.4 特殊值与异常
-- 检测无效操作
if X /= X then -- 只有 NaN 满足
Ada.Text_IO.Put_Line ("X is NaN");
end if;
-- 检测无穷大
if X > Float'Safe_Last then
Ada.Text_IO.Put_Line ("X is infinite");
end if;
四、定点类型基础
定点类型用整数运算模拟小数,精度固定,无舍入误差(但可能有溢出风险),常用于嵌入式、控制、金融等领域。
4.1 普通定点类型
使用 delta 指定最小步长(绝对精度),必须对称于0的范围:
-- 精度 0.01,范围 -100.0 .. 100.0
type Fixed is delta 0.01 range -100.0 .. 100.0;
-- 自动确定存储大小(通常为16位或32位整数)
F : Fixed := 12.34;
注意:delta 值必须是2的负幂(或编译时能转换为2的负幂),如 0.125 是允许的,但 0.1 可能不行(除非编译器支持十进制定点)。
4.2 小数定点类型
使用 delta 和 digits 共同定义,指定小数位数和总十进制位数,适合货币计算:
-- 总共10位十进制,小数点后2位(由 delta 0.01 隐含决定)
type Money is delta 0.01 digits 10;
-- 指定总位数,delta 决定小数部分位数
type Price is delta 0.001 digits 7; -- 范围 -9999.999 .. 9999.999
-- 十进制定点类型的范围自动计算
digits 指定总十进制位数(包括整数部分和小数部分),delta 指定小数部分的步长,二者必须兼容(delta <= 10.0**(-D) 其中 D 是小数位数)。
4.3 定点类型属性
| 属性 | 含义 | 示例 |
|---|---|---|
'Delta | 指定的 delta 值 | Fixed'Delta = 0.01 |
'Small | 实际存储的步长(通常等于 delta,除非使用 small 指定) | Fixed'Small |
'Fore | 整数部分所需的最小字符数(包括负号) | Fixed'Fore |
'Aft | 小数部分所需的最小字符数 | Fixed'Aft = 2 |
'Range | 类型的范围 | Fixed'Range |
'Size | 存储位数 | Fixed'Size |
五、定点类型的运算与控制
5.1 定点算术运算
with Ada.Text_IO;
procedure Fixed_Demo is
type Fixed is delta 0.01 range -100.0 .. 100.0;
A, B, C : Fixed := 10.0;
begin
A := 12.34;
B := 5.67;
C := A + B; -- 18.01
C := A - B; -- 6.67
C := A * B; -- 乘法结果可能需要转换
-- C := A * B; -- 可能编译错误:定点乘法的结果类型不确定
C := Fixed (A * B); -- 需要显式转换
end Fixed_Demo;
定点乘法返回通用定点类型(universal_fixed),需要显式转换为具体类型。
5.2 溢出与范围检查
Ada 默认启用范围检查,防止溢出:
procedure Overflow_Demo is
type Small is delta 0.1 range 0.0 .. 10.0;
S : Small := 9.9;
begin
S := S + 0.2; -- 触发 Constraint_Error!9.9+0.2=10.1 > 10.0
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("Overflow prevented");
end Overflow_Demo;
5.3 表示子句控制存储
可通过 small 和 size 精确控制定点类型的内存布局:
-- 强制步长为 2^{-8},用8位整数存储
type Angle is delta 2.0**(-8) range -Pi .. Pi;
for Angle'Small use 2.0**(-8);
for Angle'Size use 8;
5.4 定点与浮点转换
F : Float := 1.23;
D : Fixed := Fixed (F); -- 浮点转定点(可能溢出)
F2 : Float := Float (D); -- 定点转浮点
六、数值稳定性与误差控制
6.1 误差来源对比
| 类型 | 误差来源 | 特点 |
|---|---|---|
| 浮点 | 舍入误差(相对误差固定) | 范围大,精度随数值大小变化 |
| 定点 | 截断误差(绝对误差固定) | 范围受限,精度均匀 |
6.2 选择准则
- 科学计算、图形、范围极大 → 浮点
- 嵌入式控制、货币、需要均匀精度 → 定点
- 对性能要求极高且无FPU → 定点
- 需要避免舍入误差累积 → 定点可能更合适
6.3 误差控制策略
- 浮点:使用高精度类型,采用稳定算法(如 Kahan 求和)
- 定点:合理选择
delta,确保不溢出,必要时使用饱和运算
-- 饱和加法(超过范围时 clamp)
function Sat_Add (A, B : Fixed) return Fixed is
Result : constant Fixed'Base := Fixed'Base (A) + Fixed'Base (B);
begin
if Result > Fixed'Last then
return Fixed'Last;
elsif Result < Fixed'First then
return Fixed'First;
else
return Fixed (Result);
end if;
end Sat_Add;
七、完整示例:数值积分(梯形法则)
-- 文件名: numerical_integration.adb
-- 演示浮点与定点两种方式计算定积分
with Ada.Text_IO;
with Ada.Float_Text_IO;
procedure Numerical_Integration is
-- 浮点版本
function F_Float (X : Float) return Float is
begin
return 4.0 / (1.0 + X * X);
end F_Float;
function Integral_Float (N : Integer) return Float is
H : constant Float := 1.0 / Float (N);
Sum : Float := 0.0;
begin
for I in 1 .. N - 1 loop
Sum := Sum + F_Float (Float (I) * H);
end loop;
return H * ((F_Float (0.0) + F_Float (1.0)) / 2.0 + Sum);
end Integral_Float;
-- 定点版本(精度 0.0001,范围 0..4)
type Fixed is delta 0.0001 range 0.0 .. 4.0;
function F_Fixed (X : Fixed) return Fixed is
begin
return Fixed (4.0 / (1.0 + Float (X) * Float (X)));
end F_Fixed;
function Integral_Fixed (N : Integer) return Fixed is
H : constant Float := 1.0 / Float (N);
Sum : Fixed := 0.0;
begin
for I in 1 .. N - 1 loop
Sum := Sum + F_Fixed (Fixed (Float (I) * H));
end loop;
return Fixed (H) * ((F_Fixed (0.0) + F_Fixed (1.0)) / 2.0 + Sum);
end Integral_Fixed;
N : constant Integer := 1000;
begin
Ada.Text_IO.Put_Line ("Float 积分值: " & Float'Image (Integral_Float (N)));
Ada.Text_IO.Put_Line ("Fixed 积分值: " & Fixed'Image (Integral_Fixed (N)));
Ada.Text_IO.Put_Line ("真实值 π: 3.1415926535...");
end Numerical_Integration;
编译运行:
gnatmake numerical_integration.adb
./numerical_integration
输出示例:
Float 积分值: 3.14159
Fixed 积分值: 3.1415
真实值 π: 3.1415926535...
八、本课总结
- 浮点类型用
digits指定精度,适用于大范围、相对误差要求场景 - 定点类型用
delta指定步长,适用于固定绝对精度、无FPU的场合 - 浮点运算存在舍入误差,比较时应使用容差
- 定点类型需注意范围溢出,Ada的范围检查提供保护
'Small控制实际存储精度,表示子句可精确控制内存布局- 数值稳定性取决于算法选择,必要时需进行误差分析
九、课后练习
1.精度探查:编写程序输出你所用编译器下 Float、Long_Float 和自定义 digits 18 类型的 'Digits、'Mantissa、'Epsilon。
2.二次方程求解:用浮点实现二次方程求根公式,测试 a=1, b=-1000000, c=1 时的稳定性,并尝试改进算法(避免灾难性抵消)。
3.货币计算:定义type Salary is delta 0.01 digits 8;,计算年薪、月薪,测试极端值溢出行为。
4.物理单位:扩展前课的物理单位包,增加加速度类型Meters_Per_Second_Squared,并用定点实现匀加速运动位移公式。
5.比较浮点与定点:编写程序计算 1000 个 0.1 相加的结果,分别使用 Float 和 delta 0.01 的定点类型,对比结果与精确值 100.0 的误差。
十、下节预告
第11课|枚举与布尔类型
我们将:
- 深入枚举类型的定义与属性
- 掌握布尔类型的逻辑运算与短路控制
- 学习字符类型的分类与转换
- 应用枚举类型实现状态机与选择控制
关键术语表
IEEE 754:浮点数国际标准,定义单精度、双精度等格式。
digits:指定浮点类型十进制精度的关键字。
delta:指定定点类型最小步长的关键字。
'Small:定点类型实际存储步长的属性。舍入误差:浮点运算因精度有限产生的近似误差。
灾难性抵消:两个相近数相减导致有效数字严重丢失的现象。
Kahan 求和:补偿舍入误差的求和算法。
饱和运算:超出范围时截断到边界值的运算。