學習軌道
/
x86-64 Assembly
x86-64 Assembly
/
練習
/
自由工作者費率
自由工作者費率

自由工作者費率

學習練習

簡介

浮點數

浮點數是一種可以帶有小數部分的數字,例如-1.23或3.1415。

為了將這樣的數字表示成一段位元組序列,x86-64 採用IEEE-754標準。 這個標準中最常見的兩種格式是單精度浮點數與雙精度浮點數。 x86-64 兩者都支援。

單精度浮點數的寬度是 32 位元,而雙精度浮點數的寬度是 64 位元。

XMM 暫存器

在 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代表雙精度浮點數。

這些指令會以一個常數數字作為第三個運算元。 這個數字代表捨入模式:

數字 模式
0 最接近值
1 向下捨入(向下)
2 向上捨入(向上)
3 截斷

例如:

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)。 這些指令都是雙運算元,語意一如往常,即使是執行乘法或除法的指令也一樣:

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 暫存器搬移到另一個的指令是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

比較

有兩組指令可以對兩個浮點數進行比較。 它們的形式為comixx與ucomixx,其中xx是ss或sd。 兩者都會一如往常設定rflags中的旗標,語意與cmp相同。

兩者的差別在於,若兩個浮點數中有一個是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 中,前八個浮點引數會依序傳遞給函式,從xmm0到xmm7。 浮點數值會從函式以xmm0回傳,必要時則用xmm1。

所有xmm暫存器都可以自由使用,沒有任何一個是保留給呼叫者的。

記憶體

浮點數值可以用與相同大小的整數相同的語意在記憶體中定義。 NASM(The Netwide Assembler,也就是這個 track 所使用的組譯器)也允許以科學記號宣告浮點數值。

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

說明

在這個練習中,你會寫一些程式碼,幫助一位自由工作者與專案經理溝通。你的任務是提供幾個工具,用來快速計算每日費率和每月費率,也可以套用指定的折扣。

我們先在自由工作者和專案經理之間訂下幾條規則:

  • 每日費率是時薪的 8 倍。
  • 一個月有 22 個可計費天數。

有時候,自由工作者會想在每日費率上套用折扣(例如給最忠誠的客戶,或是非營利客戶)。

折扣會以代表百分比的小數表示,例如25.0(25%)。

Note

以下是浮點數指令的後綴:

後綴 運算元大小
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 根據 c 將浮點數 b 取整,並存到 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 截斷

1. 根據時薪計算每日費率

實作一個名為daily_rate的函式,根據參數時薪計算每日費率,時薪為 64 位元浮點數。 合約定義一天有 8 個可計費小時。

daily_rate(60)
// => 480.0

回傳的每日費率應該是 64 位元浮點數。

2. 計算折扣後的價格

實作一個函式apply_discount,計算折扣後的價格。 它應該接受兩個參數:原始價格和折扣百分比,兩者都是 64 位元浮點數。

apply_discount(150.0, 10.0)
// => 135.0

回傳值一律應該是 64 位元浮點數。

3. 根據時薪和折扣計算每月費率

實作一個monthly_rate函式,計算折扣後的每月費率。 它接受兩個參數:時薪和折扣百分比,兩者都是 64 位元浮點數。

monthly_rate(77.0, 10.5)
// => 12130

回傳的每月費率應該向上取整(取天花板值),成為最接近的 64 位元整數。

4. 根據預算、時薪和折扣計算完整工作天數

實作一個函式days_in_budget,參數如下:

  1. 預算,64 位元無號整數。
  2. 時薪,64 位元浮點數。
  3. 折扣百分比,64 位元浮點數。

這個函式應該回傳預算能負擔的完整工作天數。

days_in_budget(20000, 80, 11.0)
// => 35

回傳的天數應該向下取整(取地板值),成為最接近的 32 位元整數。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟
x86-64 Assembly Exercism

準備好開始 自由工作者費率 了嗎?

註冊 Exercism,透過 22 個概念130 個練習 和真人引導來學習並精通 x86-64 Assembly,全部免費。