一、计算两个浮点数真值的和
步骤
边界情况处理:NaN、0和无穷大
提取浮点数的符号位、指数和尾数
- 假设有两个浮点数 A 和 B,它们的 IEEE 32 位表示分别为:
- A: 符号位 S_A,指数 E_A,尾数 M_A;
- B: 符号位 S_B,指数 E_B,尾数 M_B。
对齐指数
- 若两个数的指数不相等,则需要调整尾数,使两个数的指数相同。
- 比较 A 和 B 的指数:(变更小的)
- 如果E_A > E_B,则将B的尾数右移E_A-E_B位,直到它们的指数对齐。
- 如果E_A < E_B,则将A的尾数右移E_B-E_A位,直到它们的指数对齐。
尾数执行加法或减法
- 如果尾数隐藏位1被右移了,那么默认补0
- 检查符号位并进行相应的运算:
- 加法:
- 如果A和B同号,则直接将尾数相加。
- 如果A和B异号,则将尾数相减,符号位为较大尾数的符号。
- 减法:
- 如果A和B异号,则同样是尾数相加,符号位为较大尾数的符号。
- 如果A和B同号,则进行尾数的减法,符号位为较大尾数的符号。
规范化结果
- 溢出处理:
- 如果尾数长度超过27位,说明需要右移尾数并增加阶码。
- 如果阶码超过255(即8位全为1),则发生溢出,返回正无穷或负无穷。
- 下溢处理:
- 如果尾数长度小于27位,需要左移尾数并减少阶码,直到尾数达到27位或阶码减为0。
- 如果阶码减为0,说明结果是非规格化数,尾数需要去掉最高位。
- eg
- 例如,如果计算结果是 0.101…(即头部是0),则需要左移尾数1位,并将指数调整(阶码-1)。
- 例如,如果计算结果是 10.101…(即头部是10),则需要右移尾数1位,并将指数调整(阶码+1)。
- 溢出处理:
截断尾数并舍入(不要求掌握,给出了函数)
- 确定舍入位:找到需要舍入的位,即尾数的第24位(对于单精度浮点数)。
- 检查舍入位及其后面的位:
- 如果舍入位是0,则直接截断,不需要舍入。
- 如果舍入位是1,则需要进一步检查其后的位。
- 根据舍入规则进行舍入:
- 如果舍入位后的所有位都是0,则直接截断。
- 如果舍入位后的位不全是0,或者舍入位前的位是奇数,则向上舍入,即尾数加1。
组合符号、指数和尾数
- 最后,将计算后的符号位、指数(加上 127 偏移量)和尾数组合成最终的 IEEE 32 位浮点数。
处理溢出/下溢情况
- 溢出处理
- 当计算结果的阶码超过了浮点数的最大可表示值时,发生溢出。处理方法通常是将结果设为正无穷或负无穷,具体取决于结果的符号。
- 下溢处理
当计算结果的阶码小于浮点数的最小可表示值时,发生下溢。处理方法通常是将结果设为0,或者在某些情况下,使用次正规数(Subnormal Numbers)来表示非常小的值。
1 | public DataType add(DataType src, DataType dest) { |
二、减法
步骤:
- 处理边界情况:
- 检查是否有NaN、0或无穷大等特殊情况,如果有,直接返回相应结果。
- 提取符号、阶码、尾数:
- 从输入的浮点数字符串中提取符号位、阶码和尾数。
- 将被减数取反:
- 将被减数的符号位取反,即正数变负数,负数变正数。
- 对齐指数:
- 若两个数的指数不相等,则需要调整尾数,使两个数的指数相同。
- 比较两个数的指数,将较小数的尾数右移,直到两个数的指数对齐。
- 尾数执行加法或减法:
- 如果符号相同,则进行尾数相加。
- 如果符号不同,则进行尾数相减,符号位为较大尾数的符号。
- 规范化结果:
- 调整尾数和阶码,使尾数的最高位为1。
- 处理溢出和下溢情况。
- 舍入:
- 根据舍入规则对结果进行舍入。
- 组合符号、指数和尾数:
- 将符号位、指数和尾数组合成最终的IEEE 754格式的32位浮点数。
1 | public DataType sub(DataType src, DataType dest) { |
三、乘法
1. 处理边界情况 (NaN, 0, INF)
- NaN (Not a Number):如果任意操作数是 NaN,结果为 NaN。
- 0 和 INF (无穷大):
- 如果任一操作数为 0,且另一个为 0 或无穷大,结果为 0。
- 如果任一操作数为无穷大,结果为无穷大(符号根据操作数的符号位确定)。
2. 提取符号、阶码、尾数
- 特殊情况处理:
- 阶码为全1:表示无穷大。根据符号位判断返回正无穷大或负无穷大。
- 阶码为全0:表示非规格化数。此时,需要将阶码加1,使其真实值变为1,以确保后续运算不会出错。
- 隐藏位的处理:
- 规格化数:尾数的最高有效位为1。
- 非规格化数:尾数的最高有效位为0。
- 尾数的位数应为27位(隐藏位+23+3个保护位)。
3. 模拟运算得到中间结果
- 符号位的计算:
- 由两个操作数的符号位决定,若符号相同则结果为正,否则为负。
- 阶码的计算
- 阶码相加后减去偏置常数(127)。
exp_result=expA+expB−127
- 尾数的计算
- 尾数相乘:使用27位无符号数相乘,结果为54位乘积。
- 由于乘法涉及两个操作数的隐藏位,乘积将有2位隐藏位。
- 尾数相乘:使用27位无符号数相乘,结果为54位乘积。
- 通过对阶码加1,间接实现小数点的左移,修正乘积尾数的误差,保证乘积尾数的隐藏位为1。
4. 规格化并舍入后返回
- 尾数规格化
- 尾数左移:如果尾数的隐藏位为0,且阶码大于0
- 需不断左移尾数并将阶码减1,直到尾数的隐藏位恢复为1或阶码减为0。
- 尾数右移:如果阶码小于0,且尾数前27位不全为0
- 需不断右移尾数并将阶码增加,直到阶码增加至0或尾数的前27位已移动至全0。
- 尾数左移:如果尾数的隐藏位为0,且阶码大于0
- 阶码规格化
- 阶码为全1:发生阶码上溢,应该返回无穷大(正负根据符号位决定)。
- 阶码为0:表示非规格化数,此时应该将尾数右移一次,使其符合非规格化数的规范。
- 阶码小于0:发生阶码下溢,返回0。
1 | //规格化过程 |
5. 舍入
- 舍入方法:通过 round 函数处理 GRS 位,确保结果符合 IEEE 754 浮点数的舍入规则。
1 | public DataType mul(DataType src, DataType dest) { |
四、除法
27位无符号数除法
- 输入参数:(dest/src)
src
:除数,是一个27位的二进制字符串。dest
:被除数,是一个27位的二进制字符串。
- 初始化:
- quotientReg:存储商,初始化为空 StringBuilder。
- divisorReg:除数,前面加上一个零,成为28位二进制字符串。
- remainderReg_str:余数,初始为被除数,前面加一个零,成为28位二进制字符串。
- 注:此处与补码除法不同,补码的商被初始化为被除数
- 特殊情况处理:
- 如果 src 为零,抛出 ArithmeticException 异常。
1
throw new ArithmeticException();
- 如果 dest 为零,返回全零的商。
- 主循环(长除法核心):
- 循环 27 次,每次执行以下操作:
- 比较余数和除数:
- 够减:如果余数大于或等于除数,商补1,并余数减去除数。
- 不够减:如果余数小于除数,商补0。
- 余数左移一位,为下一次操作准备新的余数。
- 返回商:
- 循环结束后,商的二进制结果存储在 quotientReg 中,并返回。
1 | public String div27Bit(String src, String dest) { |
1. 处理边界情况 (NaN, 0, INF)
- NaN:如果其中一个操作数是 NaN,则结果也为 NaN。
- 除数为零:
- 如果除数为零,且被除数不为零,则抛出异常。
- 如果被除数和除数都为零,则结果为 NaN。
- 无穷除以无穷:结果为 NaN。
- 0 除以非零数:结果为 0。
2. 提取符号、阶码、尾数
- 符号位:1位
- 阶码:8位
- 如果为全1(即 255),返回正/负无穷。
- 如果为全0,则表示非规格化数。需要将阶码加1,使其真实值变为1,以确保后续运算不会出错。
- 尾数:
- 隐藏位的处理:
- 规格化数:尾数的最高有效位为1。
- 非规格化数:尾数的最高有效位为0。
- 尾数的位数应为27位(隐藏位+23+3个保护位)。
3. 模拟运算得到中间结果
- 符号位:两个操作数符号相同,则结果为正,反之为负。
- 阶码的处理(除法):被除数阶码-除数阶码+127
- 尾数的处理:对尾数进行27位无符号数除法运算:
- 通过模拟 27 位无符号数的除法(使用除法算法,参考先前提到的除法步骤)
- 得到的商尾数仍然是 27 位。
- 已经符合了“1位隐藏位+23位有效位+3位保护位”的要求,所以不再需要额外的操作
4. 规格化并舍入后返回
- 与乘法相同
完整代码
1 | public DataType div(DataType src, DataType dest) { |
If you like my blog, you can approve me by scanning the QR code below.