在 32 位微处理器上,需要一个函数(高效算法)来获取uint64_t除以 10 的商和余数(与打印一样)。
爬上互联网后,在http://www.hackersdelight.org/divcMore.pdfuint32_t
我只发现了与此代码类似的东西(for )
unsigned divu10(unsigned n) {
unsigned q, r;
q = (n >> 1) + (n >> 2);
q = q + (q >> 4);
q = q + (q >> 8);
q = q + (q >> 16);
q = q >> 3;
r = n - q*10;
return q + ((r + 6) >> 4);
// return q + (r > 9);
}
出于我的目的,我将其更改为这样(实际上,我添加了一个运算符q = q + (q >> 32);
:
uint64_t divu64_10 (uint64_t n, uint32_t *rem) {
uint64_t q, r;
q = (n >> 1) + (n >> 2);
q = q + (q >> 4);
q = q + (q >> 8);
q = q + (q >> 16);
q = q + (q >> 32);
q = q >> 3;
r = n - ((q << 3) + (q << 1));
*rem = r > 9 ? r - 10 : r;
// orig: return q + ((r + 6) >> 4);
return q + (r > 9);
}
我通过在普通 64 位计算机上在此代码中输入不同的数字来检查它:
unsigned long long v;
while (scanf("%llu", &v) == 1) {
unsigned long long d;
uint32_t r;
d = divu64_10(v, &r);
printf("divu10: %llu %u (C: %llu %llu)\n",
d, r,
v / 10, v % 10);
}
看来我的改变正在奏效。
事实上,问题是 - 真正精通二进制算术的人能否确认它divu64_10()
是否正确工作?
让我们看看源代码是如何工作的。
它计算
q = n * 0x33333333 >> 33
,这大约等于n * 0.0999999999...
。然后计算误差
n - q*10
,如果小于 10,则一切正确,如果大于 10,则将结果加 1。
让我们看看错误值。
可以看出,对于
n = 10*d + r
,如果 r=0,则误差为 10。可以计算出,如果不是移位而是除法,那么误差将等于
由于
n < 2 ** 32
,结果的误差不超过 1。对于 64 位,一切都一样,只是我们考虑
n * 0x3333333333333333 >> 65
。添加
q += q >> 32
相当于乘以(2**32+1)/2**32
, 而(2**32+1)*0x33333333 = 0x3333333333333333
.从 GCC 为 ARM 10_3 调用 __aeabi_uldivmod 函数的标准除法 - 在 DWT 计数器的 269 个滴答中执行。大胆的。通过乘以小数除以 10 比移位快 18%。通过在函数中保存上下文引入了显着的延迟——操作需要太多的寄存器。
乘法除法需要 67 个时钟周期,需要 Cortex-M3/M4 或更高版本。
如果该函数生成一个完成的字符串,您可以将速度提高 13 倍。char* tail_txt 是数组的尾部,最小大小为 24 字节。