C++11 的一些數值函式庫


這篇算是簡單來記錄一下 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() 來設定、切換(參考)。

下面就是一個簡單的例子:

#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::ratioC++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_randmt19937ranlux24 等產生器可以使用。(差別在哪別問我 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 最多。

透過這樣的使用,可以透過選擇不同的分布、更細緻地去控制亂數產生的狀況,以符合自己的需求。


參考:Numerics library

對「C++11 的一些數值函式庫」的想法

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.