メンテナンス層(2026-09-01確認)。 本稿は意図的に、初代8086の1オペランド形式だけを扱います。2017年公開・2023年最終更新の本文は末尾に全文保存しました。そこにあるAnswers.comのURLは来歴を示すアーカイブ内だけに残し、メンテナンス版の説明はIntelの資料に基づいています。
Table of Contents
要点
8086では、MUL は符号なし乗算、IMUL は2の補数による符号付き乗算を行います。どちらも明示的なレジスタまたはメモリのソースオペランドを1つ取り、AL または AX をもう一方の暗黙の入力として、2倍幅の積を完全な形で返します。
- バイトのソース:
AL × r/m8 → AX - ワードのソース:
AX × r/m16 → DX:AX
したがって結果の配置は同じですが、数値の解釈は同じではありません。フラグが検査する条件も異なり、MUL は上位半分がゼロか、IMUL は上位半分が下位半分の符号拡張だけになっているかを調べます。
8086にある正確な形式
8086の乗算エンコーディングは次の4つです。ここで r/m は、明示的なソースをレジスタにもメモリオペランドにもできることを表します。
| ソース幅 | MUL | IMUL | 完全な結果 |
|---|---|---|---|
| 8ビット | F6 /4、符号なし AL × r/m8 | F6 /5、符号付き AL × r/m8 | AX(上位が AH、下位が AL) |
| 16ビット | F7 /4、符号なし AX × r/m16 | F7 /5、符号付き AX × r/m16 | DX:AX(上位が DX、下位が AX) |
Intel/NASM形式の構文では、デスティネーションレジスタは暗黙です。
mul bl ; unsigned AL × BL -> AX
imul bl ; signed AL × BL -> AX
mul word [n] ; unsigned AX × word [n] -> DX:AX
imul word [n] ; signed AX × word [n] -> DX:AX
1オペランド命令は上位半分を捨てません。CFとOFが「積は下位半分だけには収まらない」と示す場合でも、完全な16ビットまたは32ビットの結果は、仕様で定められたレジスタまたはレジスタ対に残っています。
同じビットでも数値は異なる
各命令の前に AL = 02h、バイトのソースを FFh に設定し直します。ソースのビットは同一ですが、FFh は符号なしバイトなら255、2の補数の符号付きバイトなら−1です。
| 命令 | 数学上の演算 | AX 内の完全な結果 | CFとOF |
|---|---|---|---|
MUL | 2 × 255 = 510 | 01FEh | 1(AH = 01h がゼロでないため) |
IMUL | 2 × (−1) = −2 | FFFEh | 0(AH = FFh が AL = FEh の符号拡張だから) |
このため、「アルゴリズムは同じ」だけではプログラミング上の規則として不十分です。レジスタ配置は対応していますが、符号付きの解釈によって数学上の積もオーバーフロー判定も変わります。
CFとOFが実際に表すこと
初代8086のどちらの乗算形式でも、実行後のCFとOFは同じ値になります。
- バイトの
MULではAH = 00hのときに限って両方が0、ワードのMULではDX = 0000hのときに限って両方が0です。それ以外では両方が1です。 - バイトの
IMULではAHがALの符号拡張であるときに限って両方が0、ワードのIMULではDXがAXの符号拡張であるときに限って両方が0です。それ以外では両方が1です。
つまりCF/OFがクリアなら、その命令の符号なしまたは符号付きという解釈の下で、積が下位8ビットまたは16ビットに収まります。符号付きの積で上位半分が常にゼロになる、という意味ではありません。
8086のマニュアルと現在のIntelリファレンスは、これらの命令後のSF、ZF、AF、PFをいずれも未定義としています。積の分類にこれらのフラグを使わず、ほかの条件が必要なら結果を明示的に検査してください。
実行可能な8ビット参照モデル
次の小さなPythonモデルは、02h × FFh の算術と仕様上のCF/OF規則を再現します。
def signed8(value):
return value - 0x100 if value & 0x80 else value
al, src = 0x02, 0xFF
mul_bits = al * src
mul_cf_of = int((mul_bits >> 8) != 0)
imul_value = signed8(al) * signed8(src)
imul_bits = imul_value & 0xFFFF
imul_low = imul_bits & 0xFF
imul_high = imul_bits >> 8
expected_high = 0xFF if imul_low & 0x80 else 0x00
imul_cf_of = int(imul_high != expected_high)
print(f"MUL AX={mul_bits:04X}h CF=OF={mul_cf_of}")
print(f"IMUL AX={imul_bits:04X}h CF=OF={imul_cf_of}")
期待される出力:
MUL AX=01FEh CF=OF=1
IMUL AX=FFFEh CF=OF=0
これは算術の参照モデルであり、8086エミュレーターではありません。命令デコード、例外、実行タイミング、未定義のフラグはモデル化していません。
後年のIMUL形式を8086コードと混同しない
x86アーキテクチャは8086以後も拡張されたため、現在のIntelリファレンスは1・2・3オペランド形式を同じ項目で説明しています。一方、1979年10月の8086マニュアルにある命令表とデコードガイドが掲載するのは、前述したアキュムレーター基準の1オペランド形式だけです。
imul bx ; original 8086: signed AX × BX -> DX:AX
imul cx, bx ; later two-operand form: not an original 8086 encoding
imul cx, bx, 10 ; later three-operand form: not an original 8086 encoding
CPUターゲットを制限しなければ、現代のアセンブラーは3つとも受理することがあります。本物の8086用コードを書くときは、選択したターゲットと生成されたオペコードを確認してください。現在のマニュアルでレガシーモードについて「Valid」とされていることだけから、歴史上の8086でも利用できたと推測してはいけません。
実用チェックリスト
- ビット列が符号なし値(
MUL)か、2の補数の符号付き値(IMUL)かを決めます。 - 暗黙の入力を忘れないようにします。バイトのソースなら
AL、ワードのソースならAXです。 - 下位レジスタだけでなく、
AXまたはDX:AXから完全な積を読み取ります。 - CF/OFは選んだ命令の規則に従って使い、SF、ZF、AF、PFは未定義として扱います。
- 8086を対象にするなら1オペランド形式だけを使い、生成されたエンコーディングを確認します。
一次資料
- Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 2A:`IMUL—Signed Multiply` — Intelの現在の命令項目で、1オペランド形式の結果と符号拡張テストを示し、後年の形式を別に説明しています。
- Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 2B:`MUL—Unsigned Multiply` — Intelの現在の命令項目で、暗黙のオペランド、2倍幅のデスティネーション、上位半分によるフラグ規則を示しています。
- _The 8086 Family User's Manual_、1979年10月 — Intel刊行物9800722-03をBitsaversが保存したアーカイブスキャンです。乗算の説明は印刷ページ2-36〜2-37、当時のオペコード/デコード表は第4章にあります。
---
2017年原文アーカイブ(2023年最終更新、逐語保存)
以下はWordPressエクスポートにある可視本文の全体です。リポジトリの書式に合わせ、不可視の行末空白だけを正規化しました。外部リンクと大まかな “same algorithm” という表現は歴史的来歴として保存したもので、現在の典拠として支持するものではありません。
Reference Link:
http://www.answers.com/Q/What_is_the_basic_difference_between_MUL_and_IMUL_instruction_in_8086_microprocessor
mul is used for unsigned multiplication whereas imul is used for signed multiplication. Algorithm for both are same, which is as follows:
when operand is a byte:
AX = AL * operand.
when operand is a word:
(DX AX) = AX * operand.
