Cortex M0+/GCC 带舍入的整数除法实现方案问询
无除法指令Cortex-G0内核的带舍入整数除法优化实现
核心优化思路:复用除法过程的余数,避免额外计算
Cortex-G0没有硬件除法指令,编译器会生成基于减法移位的软件除法子程序——这类实现大多会在计算商的同时算出余数(比如ARM GCC的__divsi3/__modsi3本质是同一个子程序,执行一次就能拿到商和余数)。你完全不需要单独计算余数,只要复用这个过程的结果就能实现舍入,避免重复开销。
通用舍入逻辑(四舍五入)
针对任意正负的被除数a和除数b,可以用以下代码实现,尽量用低开销的位运算代替乘法/绝对值:
int rounded_div(int a, int b) { int q = a / b; int r = a - q * b; // 用减法算余数,比直接调用%更省周期(若编译器未优化) // 判断是否需要进位:余数的2倍绝对值 >= 除数绝对值,且符号匹配时进位 if ((r << 1) >= b && r > 0) { q++; } else if ((r << 1) <= b && r < 0) { q++; } return q; }
如果是向正无穷取整(而非四舍五入),逻辑更简单:当余数非零且被除数与除数同号时,商加1,开销更低:
int ceil_div(int a, int b) { int q = a / b; int r = a - q * b; if (r != 0 && ((a > 0 && b > 0) || (a < 0 && b < 0))) { q++; } return q; }
关于反汇编分析:必须做
不同编译器(GCC/ARMCC)生成的软件除法实现差异很大,有些会把商和余数存在寄存器里返回,有些可能会重复调用除法子程序。看反汇编能帮你确认:
- 编译器是否在一次除法计算后同时输出商和余数
- 余数的存储位置,是否可以直接复用,避免额外指令
比如ARM GCC开启-O2优化后,同时使用a/b和a%b会被优化为一次子程序调用,同时获取两个值;如果没优化,你可能需要手动调整代码或内联汇编。
是否需要自行编写汇编?
- 若编译器优化到位(开启
-O2/-O3),不需要——复用商和余数的代码已经足够高效。 - 若追求极致低开销,建议写——把舍入逻辑直接嵌入除法循环,避免后续分支判断的额外周期。
以下是一个简化的Thumb-2汇编实现(32位整数,四舍五入):
; 输入:R0 = 被除数a, R1 = 除数b ; 输出:R0 = 带舍入的商 rounded_div: PUSH {R4-R6, LR} EOR R4, R0, R1 ; 计算符号位(异或结果为负则商为负) ; 取被除数和除数的绝对值 MOV R5, R0 CMP R0, #0 IT LT NEG R5, R0 MOV R6, R1 CMP R1, #0 IT LT NEG R6, R1 ; 初始化商和余数 MOV R0, #0 MOV R1, R5 ; 除法循环(移位减法) MOVS R2, #32 div_loop: LSL R1, R1, #1 LSL R0, R0, #1 CMP R1, R6 IT HS SUBS R1, R1, R6 IT HS ADD R0, R0, #1 SUBS R2, R2, #1 BNE div_loop ; 判断是否舍入:2*余数 >= 除数? LSL R1, R1, #1 CMP R1, R6 IT HS ADD R0, R0, #1 ; 恢复商的符号 CMP R4, #0 IT LT NEG R0, R0 POP {R4-R6, PC}
这个实现把舍入判断直接放在除法循环结束后,没有额外函数调用,周期开销极小。
公认的优化方案
针对无硬件除法的ARM内核,业界公认的低开销方案:
- 复用除法子程序的余数:同时使用
/和%,开启编译器优化,确保只执行一次除法计算。 - 替换高开销操作:用移位代替乘法,用符号判断代替
abs函数,减少指令数。 - 嵌入舍入逻辑:极致优化时,修改除法汇编,将舍入判断整合到除法过程中,避免后续分支。
- 场景特化:如果除数是2的幂,用移位+简单判断实现,但你这里除数是任意整数,此方法不适用。
内容的提问来源于stack exchange,提问作者spizzak
相关产品推荐
相关产品推荐

