标题:PHP中操作任意精度大小的GMP扩展学习
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