32位无符号定点二进制乘法溢出问题求助
32位定点乘法避免溢出的解决方案(PIC16汇编)
核心思路:拆分运算,避免直接计算大乘积
你的问题本质是Q2格式定点数(小数点后2位)的乘法,直接计算放大后的整数乘积会溢出32位,但可以通过拆分运算顺序,把大乘法拆分为多个小乘法+除法,所有步骤都在32位范围内完成。
以你的例子为例:
- 888.88 对应整数
888*100 + 88 = 88888 - 805.00 对应整数
805*100 + 0 = 80500
理论上乘积缩放后的值应为:(88888 * 80500) / 10000,直接计算分子会溢出32位,所以把式子拆分:
(88888 * 80500)/10000 = [(888*100 + 88) * (805*100)] / 10000 = (888*805*10000 + 88*805*100) / 10000 = 888*805 + (88*805*100)/10000 = 888*805 + (88*805)/100
每一步运算的结果都远小于32位最大值(4294967295):
- 888*805 = 714840
- 88*805 = 70840,除以100得商708,余数40(对应0.4)
- 最终结果:714840 + 708 + 0.4 = 715548.4,完全符合需求
PIC16汇编实现步骤
拆分输入定点数
- 将88888拆分为整数部分
0x0378(十进制888)和小数部分0x58(十进制88) - 将80500拆分为整数部分
0x0325(十进制805)和小数部分0x00(十进制0)
- 将88888拆分为整数部分
计算整数部分乘积
用PIC16的MULWF指令实现16位×16位乘法(通过多次8位乘法累加):; 计算888 * 805,结果存放在REG3:REG2:REG1:REG0(32位) MOVLW 0x78 ; 888的低8位 MOVWF NUM1_L MOVLW 0x03 ; 888的高8位 MOVWF NUM1_H MOVLW 0x25 ; 805的低8位 MOVWF NUM2_L MOVLW 0x03 ; 805的高8位 MOVWF NUM2_H ; 调用16位乘法子程序,结果存入PRODH:PRODL(低16位)和REG3:REG2(高16位) CALL MUL16x16(注:
MUL16x16是自定义子程序,逻辑为分别计算高×高、高×低、低×高、低×低,再累加得到32位结果)计算小数部分乘积并做除法
计算88×805,然后除以100:; 计算88 * 805,结果存放在PRODH:PRODL MOVLW 0x58 ; 88的8位值 MOVWF NUM_F MOVF NUM2_L, W MULWF NUM_F ; 88*25,结果在PRODH:PRODL MOVWF TEMP_L MOVF PRODH, W MOVWF TEMP_H MOVF NUM2_H, W MULWF NUM_F ; 88*3,结果在PRODH:PRODL ; 累加:88*(3*256 +25) =88*805 MOVF PRODL, W ADDWF TEMP_L, F MOVF PRODH, W ADDWFC TEMP_H, F ; 调用16位除以8位的除法子程序,TEMP_H:TEMP_L / 100,商存于QUOT,余数存于REM MOVLW 0x64 ; 100的十六进制是0x64 MOVWF DIVISOR CALL DIV16x8(注:
DIV16x8子程序可采用循环减法实现,或分解除法:先右移2位除以4,再用减法除以25,提升效率)合并结果
将整数乘积的结果加上除法的商,余数对应小数点后的值:; 把整数乘积的低16位(REG1:REG0)加上QUOT MOVF QUOT, W ADDWF REG0, F MOVLW 0x00 ADDWFC REG1, F ; 最终整数部分是REG3:REG2:REG1:REG0(十进制715548),余数REM=40对应0.4
通用扩展(适用于两个数都有小数的情况)
如果两个数都是Q2格式(比如a=A*100+a_frac,b=B*100+b_frac),可沿用同样拆分逻辑:
(a*b)/10000 = AB + (A*b_frac + B*a_frac)/100 + (a_frac*b_frac)/10000
每一项运算都不会溢出32位,最后合并整数部分和小数部分即可。
内容的提问来源于stack exchange,提问作者Adil Ahmed
相关产品推荐
相关产品推荐

