부동소수점은 -1.23이나 3.1415처럼 소수 부분을 가질 수 있는 수예요.
이런 수를 바이트의 나열로 표현하기 위해, x86-64는 IEEE-754 표준을 사용해요. 이 표준에서 가장 흔히 쓰이는 두 가지 형식은 단정밀도 부동소수점과 배정밀도 부동소수점이에요. x86-64는 둘 다 지원해요.
단정밀도 부동소수점은 32비트이고, 배정밀도 부동소수점은 64비트예요.
x86-64에는 부동소수점 연산을 위한 전용 레지스터가 16개 있고, 이름은 xmm0부터 xmm15까지예요.
이 레지스터들은 적어도 128비트이지만, 최근 프로세서에서는 256비트나 512비트로 늘어났어요. 하지만 실제로는 원하는 정밀도에 따라 대부분의 부동소수점 수가 가장 낮은 32비트나 64비트만 사용해요.
숫자 형식 사이를 변환하는 명령어 계열로 cvtxx2yy가 있어요.
xx와 yy는 모두 리터럴이 아니라 자리 표시자예요:
xx는 원본 피연산자의 타입을 나타내는 자리 표시자예요.yy는 대상 피연산자의 타입을 나타내는 자리 표시자예요.이 자리 표시자는 여러 형태를 가질 수 있는데, 그중에는 다음과 같은 것들이 있어요:
| placeholder | meaning |
|---|---|
si |
signed integer |
ss |
32-bit floating-point |
sd |
64-bit floating-point |
예를 들어요:
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로 변환돼요.
t가 하나 더 붙은 cvttxx2si 형식의 비슷한 명령어 계열도 있는데, 이들은 항상 결과를 버림해요.
roundxx 명령어 계열은 원본 피연산자의 부동소수점 수를 반올림한 부동소수점 수로 변환해 대상 피연산자에 저장해요.
여기서 xx는 늘 그렇듯 피연산자의 타입을 나타내는 자리 표시자예요. ss는 단정밀도, sd는 배정밀도 부동소수점을 뜻해요.
이 명령어들은 세 번째 피연산자로 상수를 받아요. 이 수는 반올림 모드를 나타내요:
| Number | Mode |
|---|---|
| 0 | Nearest |
| 1 | Floor (down) |
| 2 | Ceil (up) |
| 3 | Truncate |
예를 들어요:
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예요.
둘 다 cmp와 같은 의미로 rflags에 플래그를 설정해요.
둘의 차이는 두 부동소수점 수 중 하나가 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(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 | 버림 |
daily_rate라는 함수를 구현해서, 매개변수로 주어진 시간당 요금으로 일일 요금을 계산해요.
시간당 요금은 64비트 부동소수점 수예요.
계약에서는 하루를 청구 가능한 8시간으로 정의해요.
daily_rate(60)
// => 480.0
반환하는 일일 요금은 64비트 부동소수점 수여야 해요.
할인이 적용된 가격을 계산하는 apply_discount 함수를 구현해요.
이 함수는 원래 가격과 백분율로 나타낸 할인율, 두 개의 매개변수를 받아요. 둘 다 64비트 부동소수점 수예요.
apply_discount(150.0, 10.0)
// => 135.0
반환값은 항상 64비트 부동소수점 수여야 해요.
할인이 적용된 월 요금을 계산하는 monthly_rate 함수를 구현해요.
이 함수는 시간당 요금과 백분율로 나타낸 할인, 두 개의 매개변수를 받아요. 둘 다 64비트 부동소수점 수예요.
monthly_rate(77.0, 10.5)
// => 12130
반환하는 월 요금은 64비트 정수로 올림해야 해요.
다음 매개변수를 받는 days_in_budget 함수를 구현해요:
이 함수는 예산으로 며칠을 온전히 일할 수 있는지 그 일수를 반환해야 해요.
days_in_budget(20000, 80, 11.0)
// => 35
반환하는 일수는 32비트 정수로 내림해야 해요.
Exercism에 가입하고 x86-64 Assembly 트랙을 개념 22개연습 문제 130개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.