對于各類開發(fā)語言來說,整數(shù)都有一個最大的位數(shù),如果超過位數(shù)就無法顯示或者操作了。其實,這也是一種精度越界之后產生的精度丟失問題。在我們的 php 代碼中,最大的整數(shù)非常大,我們可以通過 PHP_INT_MAX 來查看。不過,當整數(shù)超過一定的位數(shù)之后,就會使用科學計數(shù)法來顯示了,這個可不是我們想要的結果。別著急,GMP 擴展就是專門用來應對這種情況的。
GMP 擴展是隨 PHP 源碼包一起發(fā)布的,在安裝擴展之前需要系統(tǒng)環(huán)境中先安裝 gmp-devel ,在 centos 中直接 yun install gmp-devel 就可以了。
超大數(shù)字的精度丟失問題
我們先來看看直接打印輸出超大的數(shù)字會發(fā)生什么。
echo PHP_INT_MAX; // 92233720368547758071.2312312312312E+26
$a = 123123123123123123123123123;
echo $a, PHP_EOL; // 1.2312312312312E+26
echo $a + 1, PHP_EOL; // 1.2312312312312E+26
可以看到,顯示的結果都是科學計數(shù)法的形式了。而且對于簡單的運算操作來說,也基本看不到有什么區(qū)別了。就像我們最后給 $a + 1 的情況,它和原始的數(shù)據(jù)展示 出來的結果是一樣的。
$b = gmp_init("123123123123123123123123123");
echo $b, PHP_EOL; // 123123123123123123123123123
echo gmp_add($b, 1), PHP_EOL; // 123123123123123123123123124
當我們使用 GMP 擴展后,就可以使用 gmp_init() 來實例化這樣的超大數(shù)字。打印的結果還是標準的數(shù)字格式。不過,這里需要注意的是,這個擴展其實是將我們要操作的這種超大的數(shù)字轉換成了字符串來表示。
gmp_add() 是 GMP 的加法操作函數(shù),非常簡單,就是兩個參數(shù)進行相加,然后返回的依然是一個 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 擴展也提供了一系列的運算操作函數(shù),就像我們上面已經見過了 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
這四個分別就是 減 、乘 、除 、余 的計算。非常地簡單,這里也就不多說了。在這里需要注意的一點是,它們接收的參數(shù)可以是 int 類型,也可以是 字符串 類型。就和 gmp_init() 接收的參數(shù)一樣。
echo gmp_abs("-123123123123123123123123123"), PHP_EOL; // 123123123123123123123123123
echo gmp_pow($b, 3), PHP_EOL; // 1866460784838622135378351047886265184644645186267890058355382138624840786461867
echo gmp_sqrt($b), PHP_EOL; // 11096085937082
這三個函數(shù)分別是取絕對值、乘方、二次方根的計算函數(shù)。和普通的 Math 計算函數(shù)都是類似的。
位操作
GMP 擴展還可以方便地對數(shù)據(jù)進行位操作以及二進制操作。比如位操作中的 與 、或 、異或。
echo gmp_and($b, "2222222222"), PHP_EOL; // 2151965570
echo gmp_or($b, "2222222222"), PHP_EOL; // 123123123123123123193379775
echo gmp_xor($b, "3333333333"), PHP_EOL; // 123123123123123120012088038
還可以將一個數(shù)字轉換成二進制格式導出。
echo gmp_export($b), PHP_EOL; // e?U??(c?O?
當然,也有對應的從二進制導入的函數(shù),這里我們就不做演示了。大家可以自己在文檔中查找相應的函數(shù)測試了解。
$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() 函數(shù)用于獲取二進制表示的字符中的 1 的數(shù)量。比如這段測試代碼中返回的結果。
$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() 函數(shù)則是分別查找第一個出現(xiàn)的 0 或 1 的位置。它的第二個參數(shù)是指明從哪個位置開始查找。另外,它們查找的方向都是從右向左開始查找,并且是從下標 0 的位置開始的哦。
其它運算操作
生成隨機數(shù)
echo gmp_random_range("10000000000000", "99999999999999999"), PHP_EOL; // 83490559526159213
// 12500000000
echo gmp_random_bits(99999),PHP_EOL; // 289814632948807684404778811091812938699609………………
就和普通的生成隨機數(shù)的函數(shù)一樣,只不過 GMP 擴展庫下面的這兩個函數(shù)能夠生成的數(shù)字范圍更大,而且返回的也是 GMP 對象的格式。對于 gmp_random_bits() 來說,最大的范圍是 12500000000 ,我的機子如果使用這個隨機因子的話直接就會報超出內存了。而使用 99999 這個隨機因子生成的隨機數(shù)字也已經非常大了,大家可以自己嘗試一下。
階乘
這個是普通的 Math 庫中所沒有的函數(shù)。直接幫我們計算階乘的結果,不用自己寫算法了哦。
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
素數(shù)
除了階乘之外,GMP 還提供了非常高大上的直接獲取和判斷素數(shù)的函數(shù)。一般來說,素數(shù)(質數(shù))也是面試中非常常見的算法題目,我們在面試的時候還是要掌握自己手寫的能力,但是手寫完之后能和面試官說一下 GMP 中已經有現(xiàn)成的函數(shù)了相信也會帶來一些加分。
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() 是獲取指定數(shù)字之后的下一個素數(shù)是多少。gmp_prob_prime() 則是判斷給寫的數(shù)字是否是素數(shù),它有三種結果,0 表示不是素數(shù),1 表示可能(疑似)素數(shù),2 表示確定是素數(shù)。
數(shù)據(jù)的符號信息
echo gmp_sign("500"), PHP_EOL; // 1
echo gmp_sign("-500"), PHP_EOL; // -1
echo gmp_sign("0"), PHP_EOL; // 0
最后這個 gmp_sign() 函數(shù)用來表示給定數(shù)據(jù)的符號信息,也就是正負數(shù)。它也是三種結果,1 表示正數(shù),-1 表示負數(shù),0 表示 0 。為什么會有一個特殊的 0 存在呢?因為 0 即不是正數(shù)也不是負數(shù)呀,它本身就是一個特殊的存在。
總結
關于 GMP 擴展還有很多方法并沒有一一列舉出來,在這里只是挑選了一些比較常用的內容給大家介紹一下。雖說是刷文檔,但也不能直接照搬文檔過來,所以更多的內容大家還是自行去文檔中查閱,我們學習的目的主要就是知道有這么個東西,不至于在真實的業(yè)務需求中踫到了相關的內容時抓瞎。
測試代碼:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202012/source/8.PHP中操作任意精度大小的GMP擴展學習.php
參考文檔:
https://www.php.net/manual/zh/book.gmp.php