這篇算是簡單來記錄一下 C++11 一些新增的數值函式庫的內容;C++17 和 C++20 的應該之後也會整理一下。
一般性數學函式
實際上,C++ 之前在 <cmath> 裡面,就有提供包括 std:abs()、std::log()、三角函數在內、不少基礎的數學函式庫了。而在 C++11 推出後,這部分也做了一定程度的擴展;除了支援型別的增加(這邊大多不是用 template 來實作)外,也多了包括 gamma、error function 等在內相當多的函式。
而像是要把浮點數取整數值,除了傳統的 ciel() 和 floor() 外,現在也提供了 trunc()、round() 可以使用。
另外,現在也可以使用 nearbyint() 和 rint() 這兩個系統的函式,來使用系統當下的捨入模式(rounding mode)來做數值的處理;而這個模式,則可以透過 fesetround() 來設定、切換(參考)。
<!–more–>
下面就是一個簡單的例子:
#include <iostream> #include <cmath> #include <cfenv> void output_round(float fVal) { long iVal1 = std::lrint(fVal); std::cout << iVal1 << std::endl; } int main() { float fVal = 1.23f; std::fesetround(FE_TONEAREST); output_round(fVal); // 1 std::fesetround(FE_UPWARD); output_round(fVal); // 2 }
透過設定成不同的方法,就可以快速地切換 rint() 的處理方法;如果有這種切換的需求的話,這些函式應該會滿有用的?
而除了捨入的模式之外,C++11 的「floating-point environment」也還可以針對一些浮點樹處理上的狀況做處理;透過 fetestexcept() 基本上可以知道系統在做浮點數計算的時候,有出現什麼例外。
這部分的細節可以參考《Common mathematical functions》。
std::ratio
std::ratio 是 C++11 提供的一個編譯階段的比例、或者說比較像是「分數」的樣板型別。
但是由於他整個就是設計成編譯階段的東西,所以不同的分數就是不同的型別,在執行階段都不能修改;也因此要使用上的限制很多。
下面是一個簡單的例子:
#include <iostream> #include <ratio> int main() {
using two_third = std::ratio<2, 3>;
using one_sixth = std::ratio<1, 6>;
using sum = std::ratio_add<two_third, one_sixth>;
std::cout << "2/3 + 1/6 = " << sum::num << '/' << sum::den << 'n'; }
老實說個人不知道該怎麼用他…很有可能完全不會去用吧?
亂數
以往要產生亂數,大概就是直接去呼叫 std::rand() 了?他會產生 0 到 RAND_MAX 中間的數值,結果會看起來像是亂數。
而 C++11 針對亂數的產生,加入了一個可擴展的架構,也可以指定產生的亂數的分布,這部分可以參考《Pseudo-random number generation》。
在使用上,分成 random_device、亂數產生器、亂數分布三個部分。
其中,random_device 本身也是一個亂數產生器(不需要指定 seed),直接使用 call operator 就可以產生亂數了;下面就是一個簡單的例子:
#include <iostream> #include <random> int main() { std::random_device ranDevice; for (int i = 0; i < 10; ++i) std::cout << ranDevice() << "n"; }
在規範上,他產生的亂數會是均勻分布的。
而實際上,在電腦上要產生亂數(實際上一般電腦沒有真正的亂數、都是偽隨機數)有許多不同的演算法可以做到;而在 C++11 中,就定義了 minstd_rand、mt19937、ranlux24 等產生器可以使用。(差別在哪別問我 XD)
下面就是一個使用 mt19937 的例子:
#include <iostream> #include <random> int main() { std::random_device ranDevice; std::mt19937 ranGenerator(ranDevice()); for (int i = 0; i < 10; ++i) std::cout << ranGenerator() << "n"; }
這邊要注意的是,這些亂數產生器都需要一個 random seed,如果給的 random seed 一樣,那產生的結果也都會一樣(這也就是為什麼說電腦上一般沒有真正的亂數)。
所以這邊會先透過 random_device 來產生一個隨機的 random seed,其他的亂數產生器使用。
而如果想要指定產生出來的亂數的分布的話,則就可以再透過不同的亂數分布來做處理。
下面就是一個常態分佈的例子:
#include <iostream> #include <iomanip> #include <string> #include <map> #include <random> #include <cmath> int main() {
std::random_device rd{};
std::mt19937 gen{ rd() };
std::normal_distribution<> d{ 5,2 };
std::map<int, int> hist{};
for (int n = 0; n < 10000; ++n) {
++hist[std::round(d(gen))];
}
for (auto p : hist) {
std::cout << std::setw(2)
<< p.first << ' ' << std::string(p.second / 200, '*') << 'n';
} }
它的結果會類似下面的樣子:
-3 -2 -1 0 1 * 2 *** 3 ****** 4 ******** 5 ********* 6 ******** 7 ***** 8 *** 9 * 10 11 12
基本上,產生出來的亂數會以 5 最多。
透過這樣的使用,可以透過選擇不同的分布、更細緻地去控制亂數產生的狀況,以符合自己的需求。