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、muldiv16:120〜189µs
FLOAT32 * FLOAT32 / FLOAT32:40.2 ot 135.5µs、muldiv32:1.193〜1.764ms
そして32ビットMCUの場合:
タイプ-最適化されたコード-最適化なし
UINT32:521ns-604ns
UINT64:2958ns-3313ns
FLOAT32:2563ns〜2688ns
muldiv32:6791ns-25375ns
したがって、コンパイラはこのCアルゴリズムよりも賢いです。また、ネイティブレジスタよりも整数が大きい場合よりも(FPUがなくても)float変数を使用する方が常に優れています(float32の精度はuint32よりも低く、16777217以降です)。
Edit3:わかりました。私のNビットMCUはN-bits MUL N-bits
、2Nビットの結果を生成するネイティブ命令を使用しており、2つのNビットレジスタに格納されています。
ここで、Cの実装を見つけることができます(EasyasPiのソリューションをお勧めします)
2N-bits DIV N-bits
しかし、彼らはネイティブの指示を持っていません。代わりに、ループと2Nビット変数(ここではUINT64)を使用して、gccの__udivdi3関数を使用しています。したがって、これは元の質問の解決策にはなりません。