次のコードでは、質問と同じ考え方を使用して加算と減算を実装します。唯一の実際的な違いは、私の実装では、これら 2 つの関数もキャリーイン/ボローイン ビットを取り込み、キャリーアウト/ボローアウト ビットを生成することです。
キャリーイン ビットは、加算による減算を実装するために使用されます。このビットは、キャリーアウト ビットとボローアウト ビットの正しい値を取得するのに役立ちます。基本的には、ステータス レジスタのキャリー フラグを使用して、一般的な CPU のような加算と減算を実装します。
次に、キャリー/ボロー ビットを使用して、減算による比較を実装します。演算子を使用せずに比較を実装し>=
ます。これは、ビット単位の性質ではないため、算術も考慮します。復元除算アルゴリズムを使用するため、除算関数には比較関数が必要です。
!
また、演算子の使用を避け、^1
代わりに使用します。
除算関数は、除数を 2 としてunsigned ints
、その最上位部分と最下位部分をとります。最後に、最も重要な部分を余りで置き換え、最も重要でない部分を商で置き換えます。したがって、除算とモジュロの両方を実行し、典型的な CPU のような方法 (x86DIV
命令など) で実行します。この関数は、成功すると 1 を返し、オーバーフローまたは 0 による除算で 0 を返します。
メイン関数は簡単なテストを行います。除算関数の結果を直接除算の結果と比較し、不一致の場合はエラー メッセージで終了します。
無限ループに陥ることなくunsigned long long
divisor= をテストできるように、テスト部分で使用します。UINT_MAX
被除数と除数の値の範囲全体をテストするには時間がかかりすぎる場合がありますUINT_MAX
。
コード:
#include <stdio.h>
#include <limits.h>
unsigned add(unsigned a, unsigned b, unsigned carryIn, unsigned* carryOut)
{
unsigned sum = a ^ b ^ carryIn;
unsigned carryOuts = a & b | (a | b) & carryIn;
*carryOut = 0;
if (sum & (carryOuts << 1))
sum = add(sum, carryOuts << 1, 0, carryOut);
else
sum |= carryOuts << 1;
*carryOut |= (carryOuts & (UINT_MAX / 2 + 1)) >> (sizeof(unsigned) * CHAR_BIT - 1); // +-*/ are OK in constants
return sum;
}
unsigned sub(unsigned a, unsigned b, unsigned borrowIn, unsigned* borrowOut)
{
unsigned diff = add(a, ~b, borrowIn ^ 1, borrowOut);
*borrowOut ^= 1;
return diff;
}
unsigned less(unsigned a, unsigned b)
{
unsigned borrowOut;
sub(a, b, 0, &borrowOut);
return borrowOut;
}
int udiv(unsigned* dividendh, unsigned* dividendl, unsigned divisor)
{
int i;
unsigned tmp;
if (less(*dividendh, divisor) ^ 1/* *dividendh >= divisor */)
return 0; // overflow
for (i = 0; i < sizeof(unsigned) * CHAR_BIT; i++)
{
if (less(*dividendh, UINT_MAX / 2 + 1) ^ 1/* *dividendh >= 0x80...00 */)
{
*dividendh = (*dividendh << 1) | (*dividendl >> (sizeof(unsigned) * CHAR_BIT - 1));
*dividendl <<= 1;
*dividendh = sub(*dividendh, divisor, 0, &tmp);/* *dividendh -= divisor; */
*dividendl |= 1;
}
else
{
*dividendh = (*dividendh << 1) | (*dividendl >> (sizeof(unsigned) * CHAR_BIT - 1));
*dividendl <<= 1;
if (less(*dividendh, divisor) ^ 1/* *dividendh >= divisor */)
{
*dividendh = sub(*dividendh, divisor, 0, &tmp);/* *dividendh -= divisor; */
*dividendl |= 1;
}
}
}
return 1;
}
int udiv2(unsigned* dividendh, unsigned* dividendl, unsigned divisor)
{
unsigned long long dividend =
((unsigned long long)*dividendh << (sizeof(unsigned) * CHAR_BIT)) | *dividendl;
if (*dividendh >= divisor)
return 0; // overflow
*dividendl = (unsigned)(dividend / divisor);
*dividendh = (unsigned)(dividend % divisor);
return 1;
}
int main(void)
{
unsigned long long dividend, divisor;
for (dividend = 0; dividend <= /*UINT_MAX*/0xFFFF; dividend++)
for (divisor = 0; divisor <= /*UINT_MAX*/0xFF; divisor++)
{
unsigned divh = 0, divl = (unsigned)dividend, divr = (unsigned)divisor;
unsigned divh2 = 0, divl2 = (unsigned)dividend;
printf("0x%08X/0x%08X=", divl, divr);
if (udiv(&divh, &divl, divr))
printf("0x%08X.0x%08X", divl, divh);
else
printf("ovf");
printf(" ");
if (udiv2(&divh2, &divl2, divr))
printf("0x%08X.0x%08X", divl2, divh2);
else
printf("ovf");
if ((divl != divl2) || (divh != divh2))
{
printf(" err");
return -1;
}
printf("\n");
}
return 0;
}