符号付き2進数の乗算アプリ風コード作ってみた🌻

はじめに

こんばんは。Yuinaです!

今日は符号付き2進数の掛け算を、ビット列の見た目でわかりやすく確認できる、計算アプリ風のPythonコードを作成しました。

よろしくお願いいたします!

符号付き乗算の計算手順

①2の補数表現への変換します!(準備)

計算を始める前に負の数があれば2の補数表現に直します!

1.かけられる数とかける数を、それぞれ決めたら2進数で表現します。

2.負の数の場合、絶対値のビットを反転し、1を加算します。

3.正の数の場合、そのままです。


②通常の符号なし乗算を実行します!

「①2の補数表現への変換(準備)」で用意した2つのビット列を、符号がない普通の正の数のように扱います。

1.筆算の領域で通常の掛け算をします。


③結果のビット幅(例:8ビット)を2Nに拡張します(重要!)

1.乗算の結果を格納する箱を、元のビット幅の2倍(例:8 x 8 = 16ビット)にします。


④符号拡張を適用し、最終結果を得ます!

結果のビット列が正しい2の補数になっているかを確認します。

1.もしステップ2の結果が負の数なら、左の余ったビットを1で埋めます。(符号拡張)

2.もしステップ2の結果が正の数なら、左の余ったビットを0で埋めます。

コード

def decimal_twos(value, bits):

max_signed_value = (1 << (bits - 1)) - 1
min_signed_value = -(1 << (bits - 1))

if value > max_signed_value or value < min_signed_value:
print(f"値 {value} は {bits}ビット(符号付き)で表現できないよ!")

if value < 0:
two_comp_val = (1 << bits) + value
return format(two_comp_val, f'0{bits}b')

else:
return format(value, f'0{bits}b')

def multiplier_app(num1, num2, input_bits=8):
output_bits = input_bits * 2
print(f"設定: 入力 {input_bits}ビット ➡ 結果 {output_bits}ビット")

bin1 = decimal_twos(num1, input_bits)
bin2 = decimal_twos(num2, input_bits)

print(f"STEP 1: 2の補数に変換")
print(f" {num1}: {bin1} (符号ビット: {bin1[0]})")
print(f" {num2}: {bin2} (符号ビット: {bin2[0]})")

result_decimal = num1 * num2

result_bin = decimal_twos(result_decimal, output_bits)

print(f"\nSTEP 2 & 3: 掛け算と符号拡張")
print(f" 10進数での答え: {num1} × {num2} = {result_decimal}")
print(f" {output_bits}ビットの最終ビット列:")
print(f" {result_bin}")

multiplier_app(-5, 3, input_bits=8)

解説

🌻decimal_twosメソッド

こちらのメソッドは10進数を指定ビット幅の2の補数バイナリ文字列に変換しています。

まず、表現範囲を決めます。

max_signed_value = (1 << (bits - 1)) - 1
min_signed_value = -(1 << (bits - 1))

max_signed_valueはNビットで表現できる最大値 (2^(N-1) – 1)で、min_signed_valueは Nビットで表現できる最小値 (-2^(N-1))です。

定義式計算10進数での結果2進数での表現 (8ビット)
最大値 (max_signed_value)2^(8−1)−1=2^7−1+12701111111
最小値 (min_signed_value)−2^(8−1)=−2^7−12810000000

続いて、負の数(2の補数)を計算します。

    if value < 0:
two_comp_val = (1 << bits) + value
return format(two_comp_val, f'0{bits}b')

two_comp_valはビット操作と足し算を組み合わせて、負の数を2の補数に変換するためのものです。

1<< bitsでは、桁を2bits(2N)を計算します。これは、たとえば N=8 ビットなら 256 で、これは 8 ビットで表現できる最大の数 11111111 の次の桁を意味しています。

+ value では、負の数(例:−5)を足しているので、実質的には 2N−∣value∣ という引き算になります。(例: 256+(−5)=256−5)

なぜこれで2の補数になるかというと・・・

手作業でやる 「ビット反転してから +1 する」 という計算は、(2N−1)−∣value∣+1 という式と同じだからです。

なので、コンピュータは面倒なビット反転をせずに、一回の足し算(引き算)でマイナスの数値を2の補数に変換できます。


メソッドの最後に、formatで指定ビット幅のバイナリ文字列にしています。

return format(two_comp_val, f'0{bits}b')

🌻multiplier_appメソッド

    output_bits = input_bits * 2
print(f"設定: 入力 {input_bits}ビット ➡ 結果 {output_bits}ビット")

bin1 = decimal_twos(num1, input_bits)
bin2 = decimal_twos(num2, input_bits)

このメソッドでは、まず桁あふれ防止と符号拡張のためのスペース確保のため、2Nビット化します!

拡張符号についてもう少し具体的に説明します。

例えば、8ビットの −5 は 11111011 ですよね。もし、これを 0 で拡張すると…

左端が 0 になった瞬間、この数は正の数になってしまいます。

なので、負の数のビット列を広げるときは、左端の 1 をそのままコピーして埋める必要があります。

2Nビット化の後は、num1,num2を2の補数に変換します。


result_decimal = num1 * num2

ここでは、普通に掛け算します。


    result_bin = decimal_twos(result_decimal, output_bits)

上で乗算したresult_decimaと2Nビット化されたoutput_bitsを10進数を指定ビット幅の2の補数バイナリ文字列に変換に変換します。

この時符号拡張と結果が2Nビットで表現可能かどうかチェックされます。

実行結果

結果は上のようになりました。

ありがとうございました✨

タイトルとURLをコピーしました