9.8 內建數學函式
處理浮點數的常用數學函式都住在 <cmath> 函式庫。先列總表:
| 函式 | 功能 | 例子 | long double 版 |
|---|---|---|---|
sqrt(x) |
\sqrt{x}(開根號) | sqrt(2.0) 得 1.41421\ldots |
sqrtl |
cbrt(x) |
\sqrt[3]{x}(開立方根) | cbrt(27.0) 得 3 |
cbrtl |
floor(x) |
向下取整(往小的方向) | floor(-1.2) 得 -2 |
floorl |
ceil(x) |
向上取整(往大的方向) | ceil(-1.2) 得 -1 |
ceill |
round(x) |
四捨五入到整數 | round(2.5) 得 3 |
roundl |
abs(x) |
絕對值(浮點數版) | abs(-1.2) 得 1.2 |
fabsl |
pow(a, b) |
a^b(次方) | 陷阱見下方,整數次方別用它 | powl |
log(x)、log2(x)、log10(x) |
對數(底 e、2、10) | 進階函式,先知道存在 | logl 等 |
sin(x)、cos(x)、tan(x) 等 |
三角函數 | 進階函式,先知道存在 | sinl 等 |
exp(x) |
e^x | 進階函式,先知道存在 | expl |
數學符號小抄:向下取整寫作 \lfloor x \rfloor(例如 \lfloor 3.8 \rfloor = 3、\lfloor -1.2 \rfloor = -2),向上取整寫作 \lceil x \rceil——題目敘述常直接用這兩個符號,看到就想到 floor 和 ceil。
¶參數和回傳都是浮點數
這些函式吃的是浮點數、吐出來的也是浮點數(總表最後一欄以外的名字,都是 double 版)。這件事有兩個實務後果,都要放在心上:
後果一:丟整數進去,會被悄悄轉成 double。 例如寫 sqrt(2),那個整數 2 會先隱性轉型成 double 的 2.0 再開根號。小整數這樣沒問題;但大整數在「轉成 double」這一步就會失真——double 只有約 15 \sim 16 位有效數字,塞不下十幾位的大整數(原理見 9.5)。本節後面 sqrt 的陷阱就是栽在這裡。
後果二:回傳值是 double,不是 int。 floor(3.8)、round(2.5) 算出來的是 double(3.0),不是整數。如果你寫 int k = floor(3.8);,那個 double 還會被截斷一次才存進 int——多數時候剛好對,但心裡要清楚「進去、出來都是浮點數」這回事。
¶範例程式碼
#include <iostream>
#include <cmath>
using namespace std;
int main() {
// 這些函式吃浮點數、回傳的也是浮點數
cout << sqrt(8.0) << '\n'; // 2.82843(預設 6 位有效數字)
cout << floor(3.8) << '\n'; // 3
cout << floor(-1.2) << '\n'; // -2:向下是「往負的方向」!
cout << ceil(-1.2) << '\n'; // -1
cout << round(2.5) << '\n'; // 3
cout << round(2.49999) << '\n'; // 2
cout << round(-2.5) << '\n'; // -3:四捨五入是「往遠離 0 的方向進位」
cout << abs(-1.2) << '\n'; // 1.2
return 0;
}
執行結果:
2.82843
3
-2
-1
3
2
-3
1.2
注意負數的行為:floor(-1.2) 是 -2(不是 -1!),round(-2.5) 是 -3。「向下」永遠指數線上往左,跟直覺的「去掉小數」不同——去掉小數的那個操作是 9.9 的轉型。
¶pow 的兩宗罪:慢,而且不準
同樣是「大整數丟進浮點函式」的坑,pow 還要再加一條罪。pow(a, b) 是用對數指數硬算出來的——就算 a、b 都是整數,它也走浮點數路線。代價有二:速度慢(實測比一次乘法慢了將近十倍),結果帶誤差(大數字時捨入後直接錯給你看)。
實驗:把 1^2, 2^2, 3^2, \ldots 一路算到 (10^8)^2,奇數項用減的、偶數項用加的,全部加總起來。用 pow(i, 2) 的版本:
long long an = 0;
for (long long i = 1; i <= 100000000; i++) { // i*i 會到 10^16:i 用 long long(上冊 2.9)
if (i % 2 != 0) an -= pow(i, 2);
else an += pow(i, 2);
}
在本站評測環境實測:跑了約 0.5 秒,而且答案是錯的——印出 5000000052546867(i^2 接近 10^{16} 時已超過 2^{53},pow 回傳的浮點數存不精確,加總後整個走樣)。改用一次乘法 i * i:
long long an = 0;
for (long long i = 1; i <= 100000000; i++) {
if (i % 2 != 0) an -= i * i;
else an += i * i;
}
跑約 0.06 秒,答案正確(5000000050000000)。
動手試試看:解掉地平線(練 sqrt)。記得先把 9.3 的輸出開場白寫好。