我将 Sven 的代码实现为 UINT16,以对其进行深入测试:
uint16_t muldiv16(uint16_t a, uint16_t b, uint16_t c);
int main(int argc, char *argv[]){
uint32_t a;
uint32_t b;
uint32_t c;
uint16_t r1, r2;
// ~167 days, estimated on i7 6700k, single thread.
// Split the 'a' range, to run several instances of this code on multi-cores processor
// ~1s, with an UINT8 implementation
for(a=0; a<=UINT16_MAX; a++){
for(b=0; b<=UINT16_MAX; b++){
for(c=1; c<=UINT16_MAX; c++){
r1 = uint16_t( a*b/c );
r2 = muldiv16(uint16_t(a), uint16_t(b), uint16_t(c));
if( r1 != r2 ){
std::cout << "Err: " << a << " * " << b << " / " << c << ", result: " << r2 << ", exected: " << r1 << std::endl;
return -1;
}
}
}
std::cout << a << std::endl
}
std::cout << "Done." << std::endl;
return 0;
}
不幸的是,它似乎仅限于“b”(0-2147483647)的 UINT31。
这是我的更正,这似乎有效(未在 UINT16 上完成测试,但运行了很多。在 UINT8 上完成)。
uint32_t muldiv32(uint32_t a, uint32_t b, uint32_t c)
{
uint32_t q = 0; // the quotient
uint32_t r = 0; // the remainder
uint32_t qn = b / c;
uint32_t rn = b % c;
uint32_t r_carry;
uint32_t rn_carry;
while(a)
{
if (a & 1)
{
q += qn;
r_carry = (r > UINT32_MAX-rn);
r += rn;
if (r >= c || r_carry)
{
q++;
r -= c;
}
}
a >>= 1;
qn <<= 1;
rn_carry = rn & 0x80000000UL;
rn <<= 1;
if (rn >= c || rn_carry)
{
qn++;
rn -= c;
}
}
return q;
}
编辑:一项改进,返回余数,管理回合,警告溢出,当然,管理 a、b 和 c 的 UINT32 的全部范围:
typedef enum{
ROUND_DOWNWARD=0,
ROUND_TONEAREST,
ROUND_UPWARD
}ROUND;
//remainder is always positive for ROUND_DOWN ( a * b = c * q + remainder )
//remainder is always negative for ROUND_UPWARD ( a * b = c * q - remainder )
//remainder is signed for ROUND_CLOSEST ( a * b = c * q + sint32_t(remainder) )
uint32_t muldiv32(uint32_t a, uint32_t b, uint32_t c, uint32_t *remainder, ROUND round, uint8_t *ovf)
{
uint32_t q = 0; // the quotient
uint32_t r = 0; // the remainder
uint32_t qn = b / c;
uint32_t rn = b % c;
uint32_t r_carry;
uint32_t rn_carry;
uint8_t o = 0;
uint8_t rup;
while(a)
{
if (a & 1)
{
o |= (q > UINT32_MAX-qn);
q += qn;
r_carry = (r > UINT32_MAX-rn);
r += rn;
if (r >= c || r_carry)
{
o |= (q == UINT32_MAX);
q++;
r -= c;
}
}
a >>= 1;
qn <<= 1;
rn_carry = rn & 0x80000000;
rn <<= 1;
if (rn >= c || rn_carry)
{
qn++;
rn -= c;
}
}
rup = (round == ROUND_UPWARD && r);
rup |= (round == ROUND_TONEAREST && ((r<<1) >= c || r & 0x80000000));
if(rup)
{ //round
o |= (q == UINT32_MAX);
q++;
r = (round == ROUND_UPWARD) ? c-r : r-c;
}
if(remainder)
*remainder = r;
if(ovf)
*ovf = o;
return q;
}
也许可能存在另一种方法,也许效率更高:8 位、16 位和 32 位 MCU 能够计算 64 位计算(long long int)。任何人都知道编译器如何模拟它?
编辑2:
下面是一些有趣的时序,在 8 位 MCU 上:
UINT8 x UINT8 / UINT8:3.5µs
UINT16 x UINT16 / UINT16:22.5µs,muldiv8:29.9 至 45.3µs
UINT32 x UINT32 / UINT32:84µs,multidiv16:120 至 189µs
FLOAT32 * FLOAT32 / FLOAT32:40.2 到 135.5µs,muldiv32:1.193 到 1.764ms
在 32 位 MCU 上:
类型 - 优化代码 - 未优化
UINT32:521ns - 604ns
UINT64:2958ns - 3313ns
FLOAT32:2563ns - 2688ns
muldiv32:6791ns - 25375ns
所以,编译器比这个 C 算法聪明。并且使用浮点变量(即使没有 FPU)总是比使用大于本机寄存器的整数更好(即使浮点 32 的精度比 uint32 最差,从 16777217 开始)。
Edit3:好的,所以:我的 N 位 MCU 正在使用N-bits MUL N-bits
本机指令,产生 2N 位结果,存储到两个 N 位寄存器中。
在这里,您可以找到一个 C 实现(更喜欢 EasyasPi 的解决方案)
但他们没有2N-bits DIV N-bits
本地指令。相反,他们使用来自 gcc 的__udivdi3函数,带有循环和 2N 位变量(此处为 UINT64)。因此,这不能解决原始问题。