BitInt 支持位运算操作符

pull/1/head
韩天峰 3 months ago
parent 8c8daa47e5
commit 95b1c89053
  1. 99
      docs/GMP_GAP.md
  2. 32
      src/Php/CompilerBase.php
  3. 1
      src/Php/Constants.php
  4. 8
      src/Php/UniversalMethodCall.php
  5. 74
      tests/aot/bigint/bitwise.phpt
  6. 58
      tests/aot/bigint/bitwise_shift.phpt

@ -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 扩展

@ -1941,13 +1941,26 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { 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) { if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
} }
if ($rightType !== self::TYPE_BIGINT) { if ($rightType !== self::TYPE_BIGINT) {
$rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType);
} }
$arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']; $arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod', '&' => 'bitAnd', '|' => 'bitOr', '^' => 'bitXor'];
$method = $arithOpMap[$op] ?? null; $method = $arithOpMap[$op] ?? null;
if ($method) { if ($method) {
return 'php::BigInt::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')'; return 'php::BigInt::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')';
@ -2373,6 +2386,9 @@ class CompilerBase extends \PhpAot\Core\Translator
switch ($exprType) { switch ($exprType) {
case 'Expr_UnaryMinus': case 'Expr_UnaryMinus':
return $this->detectTypeOfExpr($expr->expr); 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': case 'Expr_Cast_Int':
return self::TYPE_INT; return self::TYPE_INT;
case 'Scalar_Int': case 'Scalar_Int':
@ -2564,6 +2580,9 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Expr_New': case 'Expr_New':
return self::TYPE_OBJECT; return self::TYPE_OBJECT;
case 'Expr_Assign': case 'Expr_Assign':
case 'Expr_AssignOp_BitwiseAnd':
case 'Expr_AssignOp_BitwiseOr':
case 'Expr_AssignOp_BitwiseXor':
return $this->detectVarType($expr->var); return $this->detectVarType($expr->var);
case 'Expr_Variable': case 'Expr_Variable':
return $this->detectVarType($expr); 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 protected function parseBigAssignOpExpr(string $leftExpr, string $leftType, string $rightExpr, string $rightType, string $binaryOp, NodeAbstract $errorNode, ?NodeAbstract $rightNode = null): string
{ {
[$class, $opMap] = match ($leftType) { [$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_DECIMAL => ['Decimal', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']],
self::TYPE_BIGFLOAT => ['BigFloat', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div']], 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}"); $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) { $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_DECIMAL => $this->convertDecimalExpr($rightExpr, $rightType, $rightNode),
self::TYPE_BIGFLOAT => $this->convertBigFloatExpr($rightExpr, $rightType), self::TYPE_BIGFLOAT => $this->convertBigFloatExpr($rightExpr, $rightType),
}; };
@ -3847,8 +3868,11 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBitwiseNot(Expr\BitwiseNot $expr): string 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); $var = $this->parseIdentifier($expr->expr);
return '~' . $var; return '~' . $var;
} }

@ -118,6 +118,7 @@ class Constants
'required' => false, 'required' => false,
'defaultValue' => CompilerBase::BUILD_MODE_BIN, 'defaultValue' => CompilerBase::BUILD_MODE_BIN,
], ],
// 内部开发选项,用于定位特定行的翻译问题,请勿写入用户文档
'debug-line' => [ 'debug-line' => [
'longPrefix' => 'debug-line', 'longPrefix' => 'debug-line',
'description' => 'Enable debug line', 'description' => 'Enable debug line',

@ -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], '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], '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], '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 => [ CompilerBase::TYPE_DECIMAL => [
'add' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::add', 'return_type' => CompilerBase::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], 'add' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::add', 'return_type' => CompilerBase::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1],

@ -0,0 +1,74 @@
--TEST--
BigInt bitwise operations (&, |, ^, ~, &=, |=, ^=, testBit, popCount)
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$a = std::bigInt("240"); // 0xF0
$b = std::bigInt("15"); // 0x0F
// Bitwise AND
$r1 = $a & $b;
echo ($a & $b)->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

@ -0,0 +1,58 @@
--TEST--
BigInt bitwise shift operations (<<, >>, <<=, >>=, bitShiftLeft, bitShiftRight)
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$a = std::bigInt("16"); // 0b10000
// Left shift
echo ($a << 1)->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
Loading…
Cancel
Save