PHP中操作任意精度大小的GMP擴展學習

對於各類開發語言來說,整數都有一個最大的位數,如果超過位數就無法顯示或者操作了。其實,這也是一種精度越界之後產生的精度丟失問題。在我們的 PHP 代碼中,最大的整數非常大,我們可以通過 PHP_INT_MAX 來查看。不過,當整數超過一定的位數之後,就會使用科學計數法來顯示了,這個可不是我們想要的結果。彆着急,GMP 擴展就是專門用來應對這種情況的。

GMP 擴展是隨 PHP 源碼包一起發佈的,在安裝擴展之前需要系統環境中先安裝 gmp-devel ,在 CentOS 中直接 yun install gmp-devel 就可以了。

超大數字的精度丟失問題

我們先來看看直接打印輸出超大的數字會發生什麼。

echo PHP_INT_MAX; // 92233720368547758071.2312312312312E+26

$a = 123123123123123123123123123;
echo $a, PHP_EOL; // 1.2312312312312E+26
echo $a + 1, PHP_EOL; // 1.2312312312312E+26

可以看到,顯示的結果都是科學計數法的形式了。而且對於簡單的運算操作來說,也基本看不到有什麼區別了。就像我們最後給 $a + 1 的情況,它和原始的數據展示 出來的結果是一樣的。

$b = gmp_init("123123123123123123123123123");
echo $b, PHP_EOL; // 123123123123123123123123123
echo gmp_add($b, 1), PHP_EOL; // 123123123123123123123123124

當我們使用 GMP 擴展後,就可以使用 gmp_init() 來實例化這樣的超大數字。打印的結果還是標準的數字格式。不過,這裏需要注意的是,這個擴展其實是將我們要操作的這種超大的數字轉換成了字符串來表示。

gmp_add() 是 GMP 的加法操作函數,非常簡單,就是兩個參數進行相加,然後返回的依然是一個 GMP 對象。

var_dump($b);
// object(GMP)#1 (1) {
//     ["num"]=>
//     string(27) "123123123123123123123123123"
//   }
echo $b + 1, PHP_EOL; // 123123123123123123123123124

通過打印 gmp_init() 返回的 $b 對象就可以看出來。它裏面的內容其實是一個字符串了。同時,這個對象還重寫了 __toString() 方法,所以我們可以直接 echo 它。另外,GMP 對象還重載了運算操作符,所以直接針對 GMP 對象進行日常的操作符運算也是沒有問題的。

簡單運算操作

除了重載的操作符之外,GMP 擴展也提供了一系列的運算操作函數,就像我們上面已經見過了 gmp_add() 一樣。

echo gmp_sub($b, 1), PHP_EOL; // 123123123123123123123123122
echo gmp_mul($b, 2), PHP_EOL; // 246246246246246246246246246
echo gmp_div("123123123123123123123123123", 3), PHP_EOL; // 41041041041041041041041041
echo gmp_mod($b, 5), PHP_EOL; // 3

這四個分別就是 減 、乘 、除 、餘 的計算。非常地簡單,這裏也就不多說了。在這裏需要注意的一點是,它們接收的參數可以是 int 類型,也可以是 字符串 類型。就和 gmp_init() 接收的參數一樣。

echo gmp_abs("-123123123123123123123123123"), PHP_EOL; // 123123123123123123123123123
echo gmp_pow($b, 3), PHP_EOL; // 1866460784838622135378351047886265184644645186267890058355382138624840786461867
echo gmp_sqrt($b), PHP_EOL; // 11096085937082

這三個函數分別是取絕對值、乘方、二次方根的計算函數。和普通的 Math 計算函數都是類似的。

位操作

GMP 擴展還可以方便地對數據進行位操作以及二進制操作。比如位操作中的 與 、或 、異或。

echo gmp_and($b, "2222222222"), PHP_EOL; // 2151965570
echo gmp_or($b, "2222222222"), PHP_EOL; // 123123123123123123193379775
echo gmp_xor($b, "3333333333"), PHP_EOL; // 123123123123123120012088038

還可以將一個數字轉換成二進制格式導出。

echo gmp_export($b), PHP_EOL; // e�U��(c�O�

當然,也有對應的從二進制導入的函數,這裏我們就不做演示了。大家可以自己在文檔中查找相應的函數測試瞭解。

$pop1 = gmp_init("10000101", 2); // 3
echo gmp_popcount($pop1), PHP_EOL;
$pop2 = gmp_init("11111110", 2); // 7
echo gmp_popcount($pop2), PHP_EOL;

gmp_popcount() 函數用於獲取二進制表示的字符中的 1 的數量。比如這段測試代碼中返回的結果。

$s1 = gmp_init("10111", 2);
echo gmp_scan0($s1, 0), PHP_EOL; // 3

$s2 = gmp_init("101110000", 2);
echo gmp_scan0($s2, 0), PHP_EOL; // 0

$s1 = gmp_init("10111", 2);
echo gmp_scan1($s1, 0), PHP_EOL; // 0

$s2 = gmp_init("101110000", 2);
echo gmp_scan1($s2, 0), PHP_EOL; // 4

gmp_scan0() 和 gmp_scan1() 函數則是分別查找第一個出現的 0 或 1 的位置。它的第二個參數是指明從哪個位置開始查找。另外,它們查找的方向都是從右向左開始查找,並且是從下標 0 的位置開始的哦。

其它運算操作

生成隨機數

echo gmp_random_range("10000000000000", "99999999999999999"), PHP_EOL; // 83490559526159213
// 12500000000
echo gmp_random_bits(99999),PHP_EOL; // 289814632948807684404778811091812938699609………………

就和普通的生成隨機數的函數一樣,只不過 GMP 擴展庫下面的這兩個函數能夠生成的數字範圍更大,而且返回的也是 GMP 對象的格式。對於 gmp_random_bits() 來說,最大的範圍是 12500000000 ,我的機子如果使用這個隨機因子的話直接就會報超出內存了。而使用 99999 這個隨機因子生成的隨機數字也已經非常大了,大家可以自己嘗試一下。

階乘

這個是普通的 Math 庫中所沒有的函數。直接幫我們計算階乘的結果,不用自己寫算法了哦。

echo gmp_fact(5), PHP_EOL; // 120    5*4*3*2*1=120
echo gmp_fact(50), PHP_EOL; // 30414093201713378043612608166064768844377641568960512000000000000 50*49*48…………*2*1

素數

除了階乘之外,GMP 還提供了非常高大上的直接獲取和判斷素數的函數。一般來說,素數(質數)也是面試中非常常見的算法題目,我們在面試的時候還是要掌握自己手寫的能力,但是手寫完之後能和麪試官說一下 GMP 中已經有現成的函數了相信也會帶來一些加分。

echo gmp_nextprime(10), PHP_EOL; // 11
echo gmp_nextprime(1000), PHP_EOL; // 1009
echo gmp_prob_prime(6), PHP_EOL; // 0
echo gmp_prob_prime("1111111111111111111"), PHP_EOL; // 1
echo gmp_prob_prime(7), PHP_EOL; // 2

gmp_nextprime() 是獲取指定數字之後的下一個素數是多少。gmp_prob_prime() 則是判斷給寫的數字是否是素數,它有三種結果,0 表示不是素數,1 表示可能(疑似)素數,2 表示確定是素數。

數據的符號信息

echo gmp_sign("500"), PHP_EOL; // 1
echo gmp_sign("-500"), PHP_EOL; // -1
echo gmp_sign("0"), PHP_EOL; // 0

最後這個 gmp_sign() 函數用來表示給定數據的符號信息,也就是正負數。它也是三種結果,1 表示正數,-1 表示負數,0 表示 0 。爲什麼會有一個特殊的 0 存在呢?因爲 0 即不是正數也不是負數呀,它本身就是一個特殊的存在。

總結

關於 GMP 擴展還有很多方法並沒有一一列舉出來,在這裏只是挑選了一些比較常用的內容給大家介紹一下。雖說是刷文檔,但也不能直接照搬文檔過來,所以更多的內容大家還是自行去文檔中查閱,我們學習的目的主要就是知道有這麼個東西,不至於在真實的業務需求中踫到了相關的內容時抓瞎。

測試代碼:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202012/source/8.PHP中操作任意精度大小的GMP擴展學習.php

參考文檔:

https://www.php.net/manual/zh/book.gmp.php

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章