From 95b1c89053234cf76102262e534ac694c9f73607 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 28 May 2026 18:20:51 +0800 Subject: [PATCH] =?UTF-8?q?BitInt=20=E6=94=AF=E6=8C=81=E4=BD=8D=E8=BF=90?= =?UTF-8?q?=E7=AE=97=E6=93=8D=E4=BD=9C=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/GMP_GAP.md | 99 +++++++++++++++++++++++++++++ src/Php/CompilerBase.php | 32 ++++++++-- src/Php/Constants.php | 1 + src/Php/UniversalMethodCall.php | 8 +++ tests/aot/bigint/bitwise.phpt | 74 +++++++++++++++++++++ tests/aot/bigint/bitwise_shift.phpt | 58 +++++++++++++++++ 6 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 docs/GMP_GAP.md create mode 100644 tests/aot/bigint/bitwise.phpt create mode 100644 tests/aot/bigint/bitwise_shift.phpt diff --git a/docs/GMP_GAP.md b/docs/GMP_GAP.md new file mode 100644 index 00000000..7c31d87b --- /dev/null +++ b/docs/GMP_GAP.md @@ -0,0 +1,99 @@ +# GMP 函数对照表(未实现) + +本文档记录 PHP GMP 扩展中尚未在 BigInt 类型中实现的函数,作为后续开发的参考。 + +## 统计 + +GMP 扩展共 44 个函数(不含 `gmp_init` 和别名 `gmp_div`)。已覆盖 17 个,未覆盖 27 个。 + +## 已实现 + +| GMP 函数 | BigInt 方法 / 运算符 | 说明 | +|----------|---------------------|------| +| `gmp_init` | `std::bigInt()` | 构造 | +| `gmp_add` | `add()` / `+` | 加法 | +| `gmp_sub` | `sub()` / `-` | 减法 | +| `gmp_mul` | `mul()` / `*` | 乘法 | +| `gmp_div_q` | `div()` / `/` | 除法(商) | +| `gmp_div_r` | `mod()` / `%` | 除法(余数) | +| `gmp_div_qr` | `divmod()` | 商和余数 | +| `gmp_mod` | `mod()` / `%` | 取模 | +| `gmp_pow` | `pow()` | 幂运算 | +| `gmp_powm` | `powmod()` | 模幂 | +| `gmp_neg` | `neg()` / `-`(一元) | 取负 | +| `gmp_abs` | `abs()` | 绝对值 | +| `gmp_sqrt` | `sqrt()` | 平方根 | +| `gmp_gcd` | `gcd()` | 最大公约数 | +| `gmp_cmp` | `cmp()` / `<=>` | 比较 | +| `gmp_and` | `bitAnd()` / `&` | 按位与 | +| `gmp_or` | `bitOr()` / `\|` | 按位或 | +| `gmp_xor` | `bitXor()` / `^` | 按位异或 | +| `gmp_com` | `bitNot()` / `~` | 按位取反 | +| `gmp_testbit` | `testBit()` | 位测试 | +| `gmp_popcount` | `popCount()` | 人口统计 | +| `gmp_intval` | `toInt()` | 转 int | +| `gmp_strval` | `toString()` | 转字符串 | + +## 未实现(按优先级排序) + +### 高优先级 — 常用数论函数 + +| GMP 函数 | 建议方法名 | 签名 | 说明 | +|----------|-----------|------|------| +| `gmp_sign` | `sign()` | `(): int` | 符号,返回 -1/0/1 | +| `gmp_lcm` | `lcm($x)` | `(BigInt): BigInt` | 最小公倍数 | +| `gmp_perfect_square` | `perfectSquare()` | `(): bool` | 是否完全平方数 | +| `gmp_perfect_power` | `perfectPower()` | `(): bool` | 是否完全幂 | +| `gmp_prob_prime` | `probPrime($reps = 10)` | `(int): int` | 概率素性检测(Miller-Rabin) | +| `gmp_nextprime` | `nextPrime()` | `(): BigInt` | 下一个素数 | +| `gmp_binomial` | `binomial($k)` | `(int): BigInt` | 二项式系数 C(n, k) | +| `gmp_fact` | `fact()` | `(): BigInt` | 阶乘 n! | + +### 中优先级 — 高级数论函数 + +| GMP 函数 | 建议方法名 | 签名 | 说明 | +|----------|-----------|------|------| +| `gmp_gcdext` | `gcdext($x)` | `(BigInt): array` | 扩展 GCD,返回 [g, s, t] 使得 g = s·a + t·b | +| `gmp_invert` | `invert($mod)` | `(BigInt): BigInt\|false` | 模逆元,不存在时返回 false | +| `gmp_sqrtrem` | `sqrtrem()` | `(): array` | 平方根+余数,返回 [root, rem] | +| `gmp_jacobi` | `jacobi($x)` | `(BigInt): int` | Jacobi 符号 | +| `gmp_legendre` | `legendre($x)` | `(BigInt): int` | Legendre 符号 | +| `gmp_kronecker` | `kronecker($x)` | `(BigInt): int` | Kronecker 符号 | + +### 低优先级 — 较少使用 + +| GMP 函数 | 建议方法名 | 签名 | 说明 | +|----------|-----------|------|------| +| `gmp_divexact` | `divExact($x)` | `(BigInt): BigInt` | 精确除法(已知整除时使用,比普通除法更快) | +| `gmp_root` | `root($n)` | `(int): BigInt` | n 次方根(截断) | +| `gmp_rootrem` | `rootrem($n)` | `(int): array` | n 次方根+余数 | +| `gmp_hamdist` | `hamDist($x)` | `(BigInt): int` | 汉明距离 | + +### 不适用 — 与不可变设计冲突 + +| GMP 函数 | 原因 | +|----------|------| +| `gmp_setbit` | 直接修改 GMP 对象,BigInt 不可变 | +| `gmp_clrbit` | 直接修改 GMP 对象,BigInt 不可变 | + +### 待评估 + +| GMP 函数 | 说明 | +|----------|------| +| `gmp_scan0` | 从指定位置找第一个 0 位 | +| `gmp_scan1` | 从指定位置找第一个 1 位 | +| `gmp_random_bits` | 生成随机位数的 BigInt(需全局种子,不适合实例方法) | +| `gmp_random_range` | 区间随机 BigInt(需全局种子,不适合实例方法) | +| `gmp_random_seed` | 设置随机种子(全局状态,不适合实例方法) | +| `gmp_import` | 从二进制字符串导入 | +| `gmp_export` | 导出为二进制字符串 | + +## toString 增强 + +| 缺失功能 | 说明 | +|---------|------| +| `toString($base)` | 当前 `toString()` 仅支持十进制。GMP 的 `gmp_strval` 支持 2-62 进制输出 | + +## 更新记录 + +- 2026-05-27:初始版本,对比 PHP 8.4.14 GMP 扩展 diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index c7473022..8e453aec 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1941,13 +1941,26 @@ class CompilerBase extends \PhpAot\Core\Translator } if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + // Bitwise shifts: right operand is shift amount, must stay as Int + if ($op === '<<' || $op === '>>') { + if ($leftType !== self::TYPE_BIGINT) { + $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); + } + if ($rightType === self::TYPE_BIGINT) { + $rightExpr = 'php::BigInt::toInt(' . $rightExpr . ')'; + } elseif ($rightType !== self::TYPE_INT) { + $rightExpr = $this->convertExprType($rightExpr, $rightType, self::TYPE_INT); + } + $method = ($op === '<<') ? 'bitShiftLeft' : 'bitShiftRight'; + return 'php::BigInt::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')'; + } if ($leftType !== self::TYPE_BIGINT) { $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); } if ($rightType !== self::TYPE_BIGINT) { $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); } - $arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']; + $arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod', '&' => 'bitAnd', '|' => 'bitOr', '^' => 'bitXor']; $method = $arithOpMap[$op] ?? null; if ($method) { return 'php::BigInt::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')'; @@ -2373,6 +2386,9 @@ class CompilerBase extends \PhpAot\Core\Translator switch ($exprType) { case 'Expr_UnaryMinus': return $this->detectTypeOfExpr($expr->expr); + case 'Expr_BitwiseNot': + $inner = $this->detectTypeOfExpr($expr->expr); + return $inner === self::TYPE_BIGINT ? self::TYPE_BIGINT : self::TYPE_INT; case 'Expr_Cast_Int': return self::TYPE_INT; case 'Scalar_Int': @@ -2564,6 +2580,9 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_New': return self::TYPE_OBJECT; case 'Expr_Assign': + case 'Expr_AssignOp_BitwiseAnd': + case 'Expr_AssignOp_BitwiseOr': + case 'Expr_AssignOp_BitwiseXor': return $this->detectVarType($expr->var); case 'Expr_Variable': return $this->detectVarType($expr); @@ -3122,7 +3141,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBigAssignOpExpr(string $leftExpr, string $leftType, string $rightExpr, string $rightType, string $binaryOp, NodeAbstract $errorNode, ?NodeAbstract $rightNode = null): string { [$class, $opMap] = match ($leftType) { - self::TYPE_BIGINT => ['BigInt', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']], + self::TYPE_BIGINT => ['BigInt', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod', '&' => 'bitAnd', '|' => 'bitOr', '^' => 'bitXor', '<<' => 'bitShiftLeft', '>>' => 'bitShiftRight']], self::TYPE_DECIMAL => ['Decimal', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']], self::TYPE_BIGFLOAT => ['BigFloat', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div']], }; @@ -3132,8 +3151,10 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($errorNode, "Unsupported compound assignment operator '{$binaryOp}' for type {$leftType}"); } + // For bitwise shifts, the right operand is a shift amount (Int), not BigInt + $isShift = ($binaryOp === '<<' || $binaryOp === '>>'); $convertedRight = match ($leftType) { - self::TYPE_BIGINT => $this->convertBigIntExpr($rightExpr, $rightType), + self::TYPE_BIGINT => $isShift ? $rightExpr : $this->convertBigIntExpr($rightExpr, $rightType), self::TYPE_DECIMAL => $this->convertDecimalExpr($rightExpr, $rightType, $rightNode), self::TYPE_BIGFLOAT => $this->convertBigFloatExpr($rightExpr, $rightType), }; @@ -3847,8 +3868,11 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBitwiseNot(Expr\BitwiseNot $expr): string { + $type = $this->detectTypeOfExpr($expr->expr); + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::bitNot(' . $this->parseExpr($expr->expr) . ')'; + } $var = $this->parseIdentifier($expr->expr); - return '~' . $var; } diff --git a/src/Php/Constants.php b/src/Php/Constants.php index 5e34011a..a72d8360 100644 --- a/src/Php/Constants.php +++ b/src/Php/Constants.php @@ -118,6 +118,7 @@ class Constants 'required' => false, 'defaultValue' => CompilerBase::BUILD_MODE_BIN, ], + // 内部开发选项,用于定位特定行的翻译问题,请勿写入用户文档 'debug-line' => [ 'longPrefix' => 'debug-line', 'description' => 'Enable debug line', diff --git a/src/Php/UniversalMethodCall.php b/src/Php/UniversalMethodCall.php index 0884703e..847190b1 100644 --- a/src/Php/UniversalMethodCall.php +++ b/src/Php/UniversalMethodCall.php @@ -302,6 +302,14 @@ trait UniversalMethodCall 'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toString', 'return_type' => CompilerBase::TYPE_STR, 'min_args' => 0, 'max_args' => 0], 'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toInt', 'return_type' => CompilerBase::TYPE_INT, 'min_args' => 0, 'max_args' => 0], 'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toFloat', 'return_type' => CompilerBase::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0], + 'bitAnd' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::bitAnd', 'return_type' => CompilerBase::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'bitOr' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::bitOr', 'return_type' => CompilerBase::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'bitXor' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::bitXor', 'return_type' => CompilerBase::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'bitNot' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::bitNot', 'return_type' => CompilerBase::TYPE_BIGINT, 'min_args' => 0, 'max_args' => 0], + 'testBit' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::testBit', 'return_type' => CompilerBase::TYPE_INT, 'min_args' => 1, 'max_args' => 1], + 'popCount' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::popCount', 'return_type' => CompilerBase::TYPE_INT, 'min_args' => 0, 'max_args' => 0], + 'bitShiftLeft' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::bitShiftLeft', 'return_type' => CompilerBase::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'bitShiftRight' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::bitShiftRight', 'return_type' => CompilerBase::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], ], CompilerBase::TYPE_DECIMAL => [ 'add' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::add', 'return_type' => CompilerBase::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], diff --git a/tests/aot/bigint/bitwise.phpt b/tests/aot/bigint/bitwise.phpt new file mode 100644 index 00000000..3427bc10 --- /dev/null +++ b/tests/aot/bigint/bitwise.phpt @@ -0,0 +1,74 @@ +--TEST-- +BigInt bitwise operations (&, |, ^, ~, &=, |=, ^=, testBit, popCount) +--FILE-- +toString(); echo "\n"; + + // Bitwise OR + echo ($a | $b)->toString(); echo "\n"; + + // Bitwise XOR + echo ($a ^ $b)->toString(); echo "\n"; + + // Bitwise NOT + echo (~$a)->toString(); echo "\n"; + + // testBit + echo $a->testBit(7)->toString(); echo "\n"; // bit 7 = 1 + echo $a->testBit(3)->toString(); echo "\n"; // bit 3 = 0 + echo $a->testBit(0)->toString(); echo "\n"; // bit 0 = 0 + + // popCount + echo $a->popCount()->toString(); echo "\n"; // 0xF0 has 4 ones + + // Compound AND + $c = std::bigInt("255"); // 0xFF + $c &= std::bigInt("15"); // 0x0F + echo $c->toString(); echo "\n"; + + // Compound OR + $d = std::bigInt("240"); // 0xF0 + $d |= std::bigInt("15"); // 0x0F + echo $d->toString(); echo "\n"; + + // Compound XOR + $e = std::bigInt("255"); // 0xFF + $e ^= std::bigInt("170"); // 0xAA + echo $e->toString(); echo "\n"; + + // Mixed BigInt & Int + echo ($a & 0x0F)->toString(); echo "\n"; + + // Universal method calls + echo $a->bitAnd($b)->toString(); echo "\n"; + echo $a->bitOr($b)->toString(); echo "\n"; + echo $a->bitXor($b)->toString(); echo "\n"; + echo $a->bitNot()->toString(); echo "\n"; +} +?> +--EXPECT-- +0 +255 +255 +-241 +1 +0 +0 +4 +15 +255 +85 +0 +0 +255 +255 +-241 diff --git a/tests/aot/bigint/bitwise_shift.phpt b/tests/aot/bigint/bitwise_shift.phpt new file mode 100644 index 00000000..d5b2697a --- /dev/null +++ b/tests/aot/bigint/bitwise_shift.phpt @@ -0,0 +1,58 @@ +--TEST-- +BigInt bitwise shift operations (<<, >>, <<=, >>=, bitShiftLeft, bitShiftRight) +--FILE-- +toString(); echo "\n"; // 32 + echo ($a << 3)->toString(); echo "\n"; // 128 + + // Right shift + $b = std::bigInt("128"); + echo ($b >> 1)->toString(); echo "\n"; // 64 + echo ($b >> 3)->toString(); echo "\n"; // 16 + + // Right shift truncates (integer division by 2^n) + $c = std::bigInt("15"); + echo ($c >> 1)->toString(); echo "\n"; // 7 + + // Compound shift left + $d = std::bigInt("1"); + $d <<= 10; + echo $d->toString(); echo "\n"; // 1024 + + // Compound shift right + $e = std::bigInt("1024"); + $e >>= 5; + echo $e->toString(); echo "\n"; // 32 + + // Mixed BigInt << Int + echo ($a << 4)->toString(); echo "\n"; // 256 + + // Shift by 0 + echo ($a << 0)->toString(); echo "\n"; // 16 + echo ($a >> 0)->toString(); echo "\n"; // 16 + + // Universal method calls + echo $a->bitShiftLeft(2)->toString(); echo "\n"; // 64 + echo $b->bitShiftRight(2)->toString(); echo "\n"; // 32 +} +?> +--EXPECT-- +32 +128 +64 +16 +7 +1024 +32 +256 +16 +16 +64 +32