PHP中具有任意大整数的算术运算

PHP中具有任意大整数的算术运算

Arithmetic with Arbitrarily Large Integers in PHP

好的,考虑到PHP仅本地支持32位带符号整数,因此它并不是处理任意大整数的最佳语言。我要尝试做的是创建一个类,该类可以表示任意大的二进制数,并且能够对其中两个(加/减/乘/除)进行简单的算术运算。

我的目标是处理128位整数。

我正在寻找几种方法,以及我遇到的问题。任何关于您选择什么以及如何进行的输入或评论将不胜感激。

方法1:创建一个128位整数类,将其整数内部存储为四个32位整数。这种方法的唯一问题是,在处理两个操作数的各个块时,我不确定如何处理上溢/下溢问题。

方法2:使用bcmath扩展名,因为这看起来像是它要解决的问题。我唯一担心的是bcmath扩展的缩放比例设置,因为我的128位整数中没有任何舍入错误。他们必须精确。我还担心最终能否将bcmath函数的结果转换为二进制字符串(稍后需要将其转换为某些mcrypt加密函数)。

方法#3:将数字存储为二进制字符串(可能是LSB在前)。从理论上讲,我应该能够以这种方式存储任意大小的整数。我要做的就是编写四个基本的算术函数,对两个二进制字符串执行add / sub / mult / div,并产生一个二进制字符串结果。这正是我也需要移交给mcrypt的格式,所以这是一个额外的优点。这是我认为目前最有前途的方法,但是我遇到的一个棘手问题是,PHP没有提供任何方式来操纵单个位(我知道)。我相信我必须将其分解为字节大小的块(没有双关语),这时我对处理方法1中的上溢/下溢的问题适用。


PHP GMP扩展将对此更好。另外,您可以使用它来进行十进制到二进制的转换,如下所示:

1
gmp_strval(gmp_init($n, 10), 2);

已经有各种可用的类,因此您可能希望在编写自己的解决方案之前先仔细研究一下它们(如果确实需要编写自己的解决方案)。


据我所知,bcmath扩展名是您想要的扩展名。 PHP手册中的数据有些稀疏,但是您可以通过使用bcscale()函数或大多数其他bcmath函数中的可选第三个参数来将精度设置为恰好需要的精度。对二进制字符串的事情不太确定,但是有些谷歌搜索告诉我您应该可以通过使用pack()函数来处理。


我实施了以下PEMDAS投诉BC评估程序,这可能对您有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
function BC($string, $precision = 32)
{
    if (extension_loaded('bcmath') === true)
    {
        if (is_array($string) === true)
        {
            if ((count($string = array_slice($string, 1)) == 3) && (bcscale($precision) === true))
            {
                $callback = array('^' => 'pow', '*' => 'mul', '/' => 'div', '%' => 'mod', '+' => 'add', '-' => 'sub');

                if (array_key_exists($operator = current(array_splice($string, 1, 1)), $callback) === true)
                {
                    $x = 1;
                    $result = @call_user_func_array('bc' . $callback[$operator], $string);

                    if ((strcmp('^', $operator) === 0) && (($i = fmod(array_pop($string), 1)) > 0))
                    {
                        $y = BC(sprintf('((%1$s * %2$s ^ (1 - %3$s)) / %3$s) - (%2$s / %3$s) + %2$s', $string = array_shift($string), $x, $i = pow($i, -1)));

                        do
                        {
                            $x = $y;
                            $y = BC(sprintf('((%1$s * %2$s ^ (1 - %3$s)) / %3$s) - (%2$s / %3$s) + %2$s', $string, $x, $i));
                        }

                        while (BC(sprintf('%s > %s', $x, $y)));
                    }

                    if (strpos($result = bcmul($x, $result), '.') !== false)
                    {
                        $result = rtrim(rtrim($result, '0'), '.');

                        if (preg_match(sprintf('~[.][9]{%u}$~', $precision), $result) > 0)
                        {
                            $result = bcadd($result, (strncmp('-', $result, 1) === 0) ? -1 : 1, 0);
                        }

                        else if (preg_match(sprintf('~[.][0]{%u}[1]$~', $precision - 1), $result) > 0)
                        {
                            $result = bcmul($result, 1, 0);
                        }
                    }

                    return $result;
                }

                return intval(version_compare(call_user_func_array('bccomp', $string), 0, $operator));
            }

            $string = array_shift($string);
        }

        $string = str_replace(' ', '', str_ireplace('e', ' * 10 ^ ', $string));

        while (preg_match('~[(]([^()]++)[)]~', $string) > 0)
        {
            $string = preg_replace_callback('~[(]([^()]++)[)]~', __FUNCTION__, $string);
        }

        foreach (array('\^', '[\*/%]', '[\+-]', '[<>]=?|={1,2}') as $operator)
        {
            while (preg_match(sprintf('~(?<![0-9])(%1$s)(%2$s)(%1$s)~', '[+-]?(?:[0-9]++(?:[.][0-9]*+)?|[.][0-9]++)', $operator), $string) > 0)
            {
                $string = preg_replace_callback(sprintf('~(?<![0-9])(%1$s)(%2$s)(%1$s)~', '[+-]?(?:[0-9]++(?:[.][0-9]*+)?|[.][0-9]++)', $operator), __FUNCTION__, $string, 1);
            }
        }
    }

    return (preg_match('~^[+-]?[0-9]++(?:[.][0-9]++)?$~', $string) > 0) ? $string : false;
}

它会自动处理舍入误差,只需将精度设置为所需的任何数字即可。


推荐阅读