浮動小数点数とは、-1.23や3.1415のように小数部を持てる数値です。
そのような数値をバイト列として表すために、x86-64ではIEEE-754規格を使用します。 この規格で最も一般的な2つの形式は、単精度浮動小数点数と倍精度浮動小数点数です。 x86-64は両方をサポートしています。
単精度浮動小数点数は32ビット幅で、倍精度は64ビット幅です。
x86-64には、浮動小数点演算を行うための専用レジスタが16個あり、xmm0からxmm15という名前が付いています。
これらのレジスタは少なくとも128ビット幅ですが、最近のプロセッサーではこれが256ビットや512ビットに拡張されています。 ただし、実際には、ほとんどの浮動小数点数は、必要な精度に応じて、最下位の32ビットまたは64ビットしか使いません。
数値形式を変換するための命令群があります:cvtxx2yy。
xxとyyはどちらもリテラルではなく、プレースホルダーです:
xxはソースオペランドの型を表すプレースホルダーです。yyはデスティネーションオペランドの型を表すプレースホルダーです。このプレースホルダーは多くの形を取ることができ、その一部は次のとおりです:
| プレースホルダー | 意味 |
|---|---|
si |
符号付き整数 |
ss |
32ビット浮動小数点数 |
sd |
64ビット浮動小数点数 |
例えば:
cvtsi2ss xmm0, eax ; converts a 32-bit signed integer to a single-precision floating-point
cvtsd2si rax, xmm1 ; converts a double-precision floating-point to a 64-bit signed integer
32ビット整数を64ビット浮動小数点数に変換したり、64ビット整数を32ビット浮動小数点数に変換したりできることに注意してください。
同様に、32ビット浮動小数点数を64ビット整数に変換したり、64ビット浮動小数点数を32ビット整数に変換したりすることもできます。
言い換えれば、cvtxx2yyの両オペランドのサイズは_同じである必要はありません_:
cvtsi2sd xmm0, eax ; converts a 32-bit signed integer to a double-precision floating-point
cvtsi2ss xmm0, rax ; converts a 64-bit signed integer to a single-precision floating-point
cvtss2si rax, xmm0 ; converts a single-precision floating-point to a 64-bit signed integer
cvtsd2si eax, xmm0 ; converts a double-precision floating-point to a 32-bit signed integer
浮動小数点数を整数として正確に表せない場合、cvtxx2siはmxcsrと呼ばれる特殊なレジスタの丸め制御に従って数値を丸めます。
これは、最近接への丸め、切り下げ、切り上げ、切り捨てのいずれかです。
関数に入った時点で、保証されたデフォルトはありません。
正確な結果は丸められず、したがって丸め制御の影響を受けないことに注意してください。
たとえば、42.0は_常に_42に変換されます。
結果を常に切り捨てる、cvttxx2siという形式(余分なtが付く)の同様の命令群があります。
roundxx命令群は、ソースオペランドの浮動小数点数を、デスティネーションオペランドに格納される丸められた浮動小数点数に変換します。
ここで、xxはいつものようにオペランドの型を表すプレースホルダーです。単精度ならss、倍精度浮動小数点数ならsdです。
これらの命令は、第3オペランドとして定数を受け取ります。 この数値は丸めモードを示します:
| 数値 | モード |
|---|---|
| 0 | 最近接 |
| 1 | 負の無限大方向(切り下げ) |
| 2 | 正の無限大方向(切り上げ) |
| 3 | 0方向(切り捨て) |
例えば:
roundss xmm0, xmm1, 1 ; this rounds down (takes the floor) a 32-bit floating-point in xmm1 and stores it in xmm0
roundsd xmm2, xmm2, 2 ; this rounds up (takes the ceil) a 64-bit floating-point in xmm2 and stores it in xmm2
これらの命令は正確な結果を生成するため、cvtxx2siの前準備として使用できます。
浮動小数点数を使う多くの演算では、整数用の同等の命令と似た名前の命令を、末尾に必要なプレースホルダー(ssまたはsd)を付けて使います。
これらの命令は、乗算や除算を行うものも含め、通常のセマンティクスで2オペランドです:
addss xmm1, xmm2 ; xmm1 = xmm1 + xmm2 (all 32-bit floating-point)
mulsd xmm4, xmm5 ; xmm4 = xmm4 * xmm5 (all 64-bit floating-point)
divss xmm0, xmm3 ; xmm0 = xmm0 / xmm3 (all 32-bit floating-point)
XMMレジスタから別のXMMレジスタへ移動する命令は、movssまたはmovsdです:
movss xmm1, xmm0 ; this copies a 32-bit floating-point from xmm0 to xmm1
movsd xmm6, xmm4 ; this copies a 64-bit floating-point from xmm4 to xmm6
即値(定数の符号付き整数)をXMMレジスタに直接移動することはできません。
この数値はまず汎用レジスタに移動し、次にcvtsi2xxを使って変換する必要があります:
mov rax, 10 ; rax is a 64-bit integer with value 10
cvtsi2sd xmm0, rax ; xmm0 is a 64-bit floating-point with value 10.0
2つの浮動小数点数を比較する命令には2つのグループがあります。
それらはcomixxとucomixxという形式を取り、xxはssまたはsdのどちらかです。
どちらも通常どおりrflagsのフラグを設定し、cmpと同じセマンティクスに従います。
両者の違いは、2つの浮動小数点数の一方がNaN値を持つ場合、ucomixxはparity flag (PF)を設定するのに対し、comixxはこの場合に例外を発生させることです。
浮動小数点値の比較は_符号なし_です:
ucomisd xmm0, xmm1
ja .greater ; 'ja' must be used for "greater", not 'jg'
jb .lesser ; 'jb' must be used for "lesser", not 'jl'
System V ABIでは、最初の8つの浮動小数点引数は、xmm0からxmm7の順に関数へ渡されます。
浮動小数点値は、関数からxmm0で返され、必要に応じてxmm1でも返されます。
すべてのxmmレジスタは自由に使用でき、呼び出し元のために予約されているものはありません。
浮動小数点値は、同じサイズの整数と同じセマンティクスでメモリ内に定義できます。 NASM(The Netwide Assembler、このトラックで使われているアセンブラー)では、浮動小数点値を指数表記で宣言することもできます。
section .data
pi dq 3.141592653589793 ; this is a double-precision floating-point value
exp_num dd 1.e5 ; this is a single precision floating-point value equal to 1 * 10⁵
exp_num2 dd 1.e+5 ; this is a single precision floating-point value equal to 1 * 10⁵
frac dq 1.e-10 ; this is a double precision floating-point value equal to 1 * 10⁻¹⁰, i.e., 0.0000000001
この演習では、フリーランサーがプロジェクトマネージャーとやり取りできるようにするコードを書きます。 課題は、日額と月額をすばやく計算するためのユーティリティをいくつか用意することです。割引が指定されていれば、それも適用できます。
まず、フリーランサーとプロジェクトマネージャーの間で、いくつかのルールを決めておきます。
フリーランサーが、日額に割引を適用することがあります(たとえば、最も熱心な顧客や非営利の顧客に対して)。
割引は、百分率を表す小数として扱います。たとえば25.0(25%)です。
これは、浮動小数点命令のサフィックスです。
| サフィックス | オペランドサイズ |
|---|---|
ss |
32ビット |
sd |
64ビット |
このコンセプトで登場する命令をいくつか挙げます。
| 命令 | 説明 |
|---|---|
| movss/movsd a, b | bをaにコピーします |
| addss/addsd a, b | a = a + b |
| subss/subsd a, b | a = a - b |
| mulss/mulsd a, b | a = a * b |
| divss/divsd a, b | a = a / b |
| cvtsi2ss/cvtsi2sd a, b | 整数bを浮動小数点aに変換します |
| roundss/roundsd a, b, c | 浮動小数点bをcに従って丸め、aに格納します |
| cvtss2si/cvtsd2si a, b | 浮動小数点bを整数aに変換します |
| comiss/comisd a, b | aとbを比較します。NaNの場合は例外が発生します |
| ucomiss/ucomisd a, b | aとbを比較します。NaNの場合はPFを設定します |
roundssとroundsdで使う丸め制御の数値は次のとおりです。
| 数値 | モード |
|---|---|
| 0 | 最近接 |
| 1 | 切り下げ(下方向) |
| 2 | 切り上げ(上方向) |
| 3 | ゼロ方向への切り捨て |
時給を64ビットの浮動小数点数として受け取り、日額を計算するdaily_rateという関数を実装します。
契約では、1日は請求可能な8時間と定められています。
daily_rate(60)
// => 480.0
戻り値の日額は、64ビットの浮動小数点数にします。
割引後の価格を計算するapply_discount関数を実装します。
元の価格と割引率(パーセント)の2つを入力として受け取ります。どちらも64ビットの浮動小数点数です。
apply_discount(150.0, 10.0)
// => 135.0
戻り値は常に、64ビットの浮動小数点数にします。
割引後の月額を計算するmonthly_rate関数を実装します。
時給と割引率(パーセント)の2つを入力として受け取ります。どちらも64ビットの浮動小数点数です。
monthly_rate(77.0, 10.5)
// => 12130
戻り値の月額は、64ビット整数に切り上げます(天井関数)。
次の3つを入力として受け取るdays_in_budget関数を実装します。
この関数は、予算でまかなえる完全な労働日数を返します。
days_in_budget(20000, 80, 11.0)
// => 35
戻り値の日数は、32ビット整数に切り下げます(床関数)。
Exercismに登録すれば、22個のコンセプト130個の演習、そして本物の人間によるメンタリングとともに、x86-64 Assemblyを学んでマスターできます。すべて無料です。