feat(compiler): 添加大整数、小数和大浮点数类型支持

- 在编译器基础类中添加 BigInt、Decimal 和 BigFloat 类型常量
- 实现大数字字面量自动识别逻辑,包括超过 64 位整数范围的整数字面量
- 实现高精度小数字面量识别,对 16 位以上有效数字的浮点数字面量进行特殊处理
- 添加算术运算符重载支持,实现 BigInt、Decimal、BigFloat 的 +、-、*、/、% 等操作
- 添加比较运算符支持,实现 <、>、<=、>=、==、!=、<=> 运算符的正确处理
- 实现不同数值类型间的转换逻辑,确保精度安全
- 添加幂运算符 (**) 对 BigInt 类型的支持
- 添加一元负号运算符对大数字类型的支持
- 实现类型检测逻辑,准确识别各种大数字类型
- 添加对 std::bigInt、std::decimal、std::bigFloat 构造函数的支持
- 添加必要的第三方库依赖,包括 GMP、libmpdec 和 MPFR 库
- 新增多组测试用例验证大数字类型的功能正确性
pull/1/head
韩天峰 3 months ago
parent 8a9a8ce138
commit ee30acc94e
  1. 406
      src/Php/CompilerBase.php
  2. 45
      src/Php/UniversalMethodCall.php
  3. 100
      tests/aot/big_number_operators.phpt
  4. 78
      tests/aot/bigfloat_operators.phpt
  5. 27
      tests/aot/bigint/arithmetic.phpt
  6. 20
      tests/aot/bigint/compare.phpt
  7. 18
      tests/aot/bigint/literal.phpt
  8. 37
      tests/aot/bigint/mixed.phpt
  9. 50
      tests/aot/std_builtin.phpt

@ -70,6 +70,9 @@ class CompilerBase extends \PhpAot\Core\Translator
public const string TYPE_ARRAY = 'php::Array';
public const string TYPE_RESOURCE = 'php::Resource';
public const string TYPE_STREAM = 'php::Stream';
public const string TYPE_BIGINT = 'php::BigInt';
public const string TYPE_DECIMAL = 'php::Decimal';
public const string TYPE_BIGFLOAT = 'php::BigFloat';
private const array STREAM_FUNCTIONS = [
'fopen',
@ -296,7 +299,10 @@ class CompilerBase extends \PhpAot\Core\Translator
protected Parser $parser;
protected PrettyPrinter $printer;
protected bool $isPhpZts = false; // PHP 是否为线程安全版本
protected bool $usesBigInt = false;
protected bool $usesDecimal = false;
protected bool $usesBigFloat = false;
// Windows 平台:保存检测到的 PHP lib 文件路径
protected string $windowsPhpEmbedLib = ''; // php8embed.lib 路径
protected string $windowsPhpCoreLib = ''; // php8ts.lib 或 php8.lib 路径
@ -1193,6 +1199,14 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Scalar_Int':
return $expr->value . $this->getPlatform()->getIntegerLiteralSuffix();
case 'Scalar_Float':
if ($this->isBigIntLiteral($expr)) {
$this->usesBigInt = true;
return 'php::newBigInt(' . $this->getLiteralString($this->getBigIntLiteralString($expr)) . ')';
}
if ($this->isDecimalLiteral($expr)) {
$this->usesDecimal = true;
return 'php::newDecimal(' . $this->getLiteralString($this->getDecimalLiteralString($expr)) . ')';
}
return $this->parseScalarFloat($expr);
case 'Scalar_String':
return $expr->hasAttribute('noLiteralString') ? $this->genCharPtr($expr->value, true) : $this->getLiteralString($expr->value);
@ -1202,6 +1216,57 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
/**
* Check if a numeric literal's rawValue represents an integer that exceeds int64 range.
* PHP's parser converts such literals to float (Scalar_Float) when they overflow.
*/
private function isBigIntLiteral(Node\Scalar $expr): bool
{
$rawValue = $expr->getAttribute('rawValue');
if ($rawValue === null) {
return false;
}
// Remove underscores
$clean = str_replace('_', '', $rawValue);
// Must look like a decimal integer (no dot, no hex/oct/bin prefix, all digits)
if (!preg_match('/^\d+$/', $clean)) {
return false;
}
// 19+ decimal digits exceed int64 range
return strlen(ltrim($clean, '0')) >= 19;
}
/**
* Check if a Scalar_Float literal should be treated as Decimal.
* Only "long" floats (>= 16 significant digits) that would lose precision
* as native PHP float (double) are auto-converted.
*/
private function isDecimalLiteral(Node\Scalar $expr): bool
{
$rawValue = $expr->getAttribute('rawValue');
if ($rawValue === null) {
return false;
}
$clean = str_replace('_', '', $rawValue);
// Must have a decimal point or exponent (not a pure integer)
if (!preg_match('/[\.eE]/', $clean)) {
return false;
}
// Count significant digits (exclude ., e, E, +, -)
$digits = preg_replace('/[^0-9]/', '', $clean);
return strlen(ltrim($digits, '0')) >= 16;
}
private function getBigIntLiteralString(Node\Scalar $expr): string
{
return str_replace('_', '', $expr->getAttribute('rawValue'));
}
private function getDecimalLiteralString(Node\Scalar $expr): string
{
return str_replace('_', '', $expr->getAttribute('rawValue'));
}
protected function parseSuperGlobalVar(string $name): string
{
if (!$this->hasGlobalVar($name)) {
@ -1822,6 +1887,74 @@ class CompilerBase extends \PhpAot\Core\Translator
$leftType = $this->detectTypeOfExpr($left);
$rightType = $this->detectTypeOfExpr($right);
if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) {
// BigFloat cannot implicitly mix with BigInt or Decimal — risk of precision loss
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->fatalError($left, 'Cannot mix BigFloat and BigInt implicitly. Use std::bigFloat() to convert explicitly.');
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->fatalError($left, 'Cannot mix BigFloat and Decimal implicitly. Use std::bigFloat() to convert explicitly.');
}
$this->usesBigFloat = true;
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGFLOAT) {
$rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType);
}
$arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div'];
$method = $arithOpMap[$op] ?? null;
if ($method) {
return 'php::BigFloat::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')';
}
$cmpOpMap = ['<' => '< 0', '>' => '> 0', '<=' => '<= 0', '>=' => '>= 0'];
if (isset($cmpOpMap[$op])) {
return 'php::toBool(php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') ' . $cmpOpMap[$op] . ')';
}
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
// BigInt and Decimal cannot implicitly mix — risk of precision loss
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->fatalError($left, 'Cannot mix BigInt and Decimal implicitly. Use std::decimal() or std::bigInt() to convert explicitly.');
}
$this->usesDecimal = true;
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
}
$arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod'];
$method = $arithOpMap[$op] ?? null;
if ($method) {
return 'php::Decimal::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')';
}
$cmpOpMap = ['<' => '< 0', '>' => '> 0', '<=' => '<= 0', '>=' => '>= 0'];
if (isset($cmpOpMap[$op])) {
return 'php::toBool(php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') ' . $cmpOpMap[$op] . ')';
}
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
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'];
$method = $arithOpMap[$op] ?? null;
if ($method) {
return 'php::BigInt::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')';
}
$cmpOpMap = ['<' => '< 0', '>' => '> 0', '<=' => '<= 0', '>=' => '>= 0'];
if (isset($cmpOpMap[$op])) {
return 'php::toBool(php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') ' . $cmpOpMap[$op] . ')';
}
}
if ($leftType === self::TYPE_FLOAT) {
$rightExpr = $this->convertExprType($rightExpr, self::TYPE_FLOAT, $rightType);
} elseif ($rightType === self::TYPE_FLOAT) {
@ -2236,12 +2369,22 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$exprType = $expr->getType();
switch ($exprType) {
case 'Expr_UnaryMinus':
return $this->detectTypeOfExpr($expr->expr);
case 'Expr_Cast_Int':
return self::TYPE_INT;
case 'Scalar_Int':
return self::TYPE_INT;
case 'Expr_Cast_Float':
case 'Expr_Cast_Double':
return self::TYPE_FLOAT;
case 'Scalar_Float':
if ($this->isBigIntLiteral($expr)) {
return self::TYPE_BIGINT;
}
if ($this->isDecimalLiteral($expr)) {
return self::TYPE_DECIMAL;
}
return self::TYPE_FLOAT;
case 'Expr_Cast_Bool':
case 'Scalar_Bool':
@ -2263,6 +2406,19 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Expr_BinaryOp_BooleanAnd':
$leftType = $this->detectTypeOfExpr($expr->left);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) {
return self::TYPE_BIGFLOAT;
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
return self::TYPE_DECIMAL;
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
if ($exprType === 'Expr_BinaryOp_Div') {
// BigInt division produces BigInt (integer division); BigDecimal in future
return self::TYPE_BIGINT;
}
return self::TYPE_BIGINT;
}
if ($leftType === self::TYPE_FLOAT || $rightType === self::TYPE_FLOAT) {
return self::TYPE_FLOAT;
}
@ -2333,6 +2489,15 @@ class CompilerBase extends \PhpAot\Core\Translator
return self::TYPE_OBJECT;
}
$className = $this->parseIdentifier($expr->class);
if ($className === 'std') {
$method = $this->parseIdentifier($expr->name);
return match ($method) {
'bigInt' => self::TYPE_BIGINT,
'decimal' => self::TYPE_DECIMAL,
'bigFloat' => self::TYPE_BIGFLOAT,
default => self::TYPE_VAR,
};
}
if ($className === 'self') {
$className = $this->getFullClassName();
} elseif ($className === 'parent') {
@ -2620,6 +2785,23 @@ class CompilerBase extends \PhpAot\Core\Translator
$libraries[] = 'php';
}
// GMP — BigInt arbitrary-precision arithmetic (only when actually used)
if ($this->usesBigInt && !$platform instanceof Windows) {
$libraries[] = 'gmp';
$libraries[] = 'gmpxx';
}
// libmpdec — Decimal arbitrary-precision decimal (only when actually used)
if ($this->usesDecimal && !$platform instanceof Windows) {
$libraries[] = 'mpdec++';
$libraries[] = 'mpdec';
}
// MPFR — BigFloat arbitrary-precision float (only when actually used)
if ($this->usesBigFloat && !$platform instanceof Windows) {
$libraries[] = 'mpfr';
}
return $libraries;
}
@ -3566,9 +3748,19 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBinaryOpPow(Expr\BinaryOp\Pow $expr): string
{
$leftType = $this->detectTypeOfExpr($expr->left);
if ($leftType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($rightType !== self::TYPE_BIGINT) {
$rightExpr = $this->convertBigIntExpr($rightExpr, $rightType);
}
return 'php::BigInt::pow(' . $leftExpr . ', ' . $rightExpr . ')';
}
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return 'php::math::pow(' . $left . ', ' . $right . ')';
}
@ -3637,11 +3829,87 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBinaryOpEqual(Expr\BinaryOp\Equal $expr): string
{
$leftType = $this->detectTypeOfExpr($expr->left);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGFLOAT) {
$rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType);
}
return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0';
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGINT) {
$rightExpr = $this->convertBigIntExpr($rightExpr, $rightType);
}
return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0';
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
}
return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0';
}
return 'php::equals(' . $this->parseCompareExpr($expr->left) . ', ' . $this->parseCompareExpr($expr->right) . ')';
}
protected function parseBinaryOpNotEqual(Expr\BinaryOp\NotEqual $expr): string
{
$leftType = $this->detectTypeOfExpr($expr->left);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGFLOAT) {
$rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType);
}
return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0';
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGINT) {
$rightExpr = $this->convertBigIntExpr($rightExpr, $rightType);
}
return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0';
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
}
return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0';
}
return '!php::equals(' . $this->parseCompareExpr($expr->left) . ', ' . $this->parseCompareExpr($expr->right) . ')';
}
@ -3709,6 +3977,45 @@ class CompilerBase extends \PhpAot\Core\Translator
return $expr;
}
protected function convertDecimalExpr(string $expr, string $fromType = ''): string
{
if ($fromType === self::TYPE_INT || $fromType === self::TYPE_FLOAT) {
return 'php::newDecimal(php::toString(' . $this->trimBrackets($expr) . '))';
}
if ($fromType === self::TYPE_BIGINT) {
return 'php::newDecimal(php::BigInt::toString(' . $this->trimBrackets($expr) . '))';
}
return $expr;
}
protected function convertBigIntExpr(string $expr, string $fromType = ''): string
{
if ($fromType === self::TYPE_INT) {
return 'php::newBigInt(' . $this->trimBrackets($expr) . ')';
}
if ($fromType === self::TYPE_FLOAT) {
return 'php::newBigInt((php::Int)' . $this->trimBrackets($expr) . ')';
}
return $expr;
}
protected function convertBigFloatExpr(string $expr, string $fromType = ''): string
{
if ($fromType === self::TYPE_INT) {
return 'php::newBigFloat(' . $this->trimBrackets($expr) . ')';
}
if ($fromType === self::TYPE_FLOAT) {
return 'php::newBigFloat(' . $this->trimBrackets($expr) . ')';
}
if ($fromType === self::TYPE_BIGINT) {
return 'php::BigFloat::newInstance(php::BigInt::toString(' . $this->trimBrackets($expr) . '))';
}
if ($fromType === self::TYPE_DECIMAL) {
return 'php::BigFloat::newInstance(php::Decimal::toString(' . $this->trimBrackets($expr) . '))';
}
return $expr;
}
protected function convertStringExpr(string $expr): string
{
if (!$this->isClosedExpr($expr, 'php::toString')) {
@ -3777,9 +4084,46 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBinaryOpSpaceship(Expr\BinaryOp\Spaceship $expr): string
{
$leftType = $this->detectTypeOfExpr($expr->left);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGFLOAT) {
$rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType);
}
return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ')';
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGINT) {
$rightExpr = $this->convertBigIntExpr($rightExpr, $rightType);
}
return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ')';
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
}
return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ')';
}
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return 'php::compare(' . $left . ', ' . $right . ')';
}
@ -4001,6 +4345,19 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseUnaryMinus(Expr\UnaryMinus $expr): string
{
$type = $this->detectTypeOfExpr($expr->expr);
if ($type === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
return 'php::BigFloat::neg(' . $this->parseExpr($expr->expr) . ')';
}
if ($type === self::TYPE_BIGINT) {
$this->usesBigInt = true;
return 'php::BigInt::neg(' . $this->parseExpr($expr->expr) . ')';
}
if ($type === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
return 'php::Decimal::neg(' . $this->parseExpr($expr->expr) . ')';
}
$code = $this->parseExpr($expr->expr);
return '-' . $code;
@ -4013,10 +4370,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBinaryOpDiv(Expr\BinaryOp\Div $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return $left . ' / (' . $right . ')';
return $this->parseBinaryOp($expr->left, $expr->right, '/');
}
protected function parseBinaryOpMinus(Expr\BinaryOp\Minus $expr): string
@ -5702,12 +6056,46 @@ class CompilerBase extends \PhpAot\Core\Translator
'int' => self::TYPE_INT,
'float' => self::TYPE_FLOAT,
'bool' => self::TYPE_BOOL,
'bigInt' => self::TYPE_BIGINT,
'decimal' => self::TYPE_DECIMAL,
'bigFloat' => self::TYPE_BIGFLOAT,
default => '',
};
if ($type) {
$expr->setAttribute('nativeType', $type);
$valueExpr = $this->parseExpr($expr->args[0]->value);
return $this->convertExprFromType($type, $valueExpr);
if (in_array($type, [self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL])) {
return $this->convertExprFromType($type, $valueExpr);
}
$argType = $this->detectTypeOfExpr($expr->args[0]->value);
if ($argType === $type) {
return $valueExpr;
}
if ($type === self::TYPE_BIGINT) {
$this->usesBigInt = true;
if ($argType === self::TYPE_INT) {
return 'php::newBigInt(' . $this->trimBrackets($valueExpr) . ')';
}
return 'php::BigInt::newInstance(' . $valueExpr . ')';
}
if ($type === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
if ($argType === self::TYPE_INT) {
return 'php::newDecimal(' . $this->trimBrackets($valueExpr) . ')';
}
return 'php::Decimal::newInstance(' . $valueExpr . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
if ($argType === self::TYPE_INT) {
return 'php::newBigFloat(' . $this->trimBrackets($valueExpr) . ')';
}
if ($argType === self::TYPE_FLOAT) {
return 'php::newBigFloat(' . $this->trimBrackets($valueExpr) . ')';
}
return 'php::BigFloat::newInstance(' . $valueExpr . ')';
}
return $valueExpr;
} else {
$this->fatalError($expr, 'Unknown std method: ' . $func);
}
@ -5807,7 +6195,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} else {
$code .= $info['decl'] . ' ' . $name . '{};';
}
} elseif ($type === self::TYPE_STREAM) {
} elseif ($type === self::TYPE_STREAM || $type === self::TYPE_BIGINT || $type === self::TYPE_DECIMAL || $type === self::TYPE_BIGFLOAT) {
$code .= self::TYPE_VAR . ' ' . $name . ';';
} else {
$code .= $type . ' ' . $name;

@ -286,11 +286,51 @@ trait UniversalMethodCall
'appendFilter' => ['handler' => 'php_fn', 'fn' => 'stream_filter_append', 'return_type' => self::TYPE_VAR, 'min_args' => 1, 'max_args' => 3],
'prependFilter' => ['handler' => 'php_fn', 'fn' => 'stream_filter_prepend', 'return_type' => self::TYPE_VAR, 'min_args' => 1, 'max_args' => 3],
],
self::TYPE_BIGINT => [
'add' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::add', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'sub' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::sub', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'mul' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::mul', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'div' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::div', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'mod' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::mod', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'pow' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::pow', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::neg', 'return_type' => self::TYPE_BIGINT, 'min_args' => 0, 'max_args' => 0],
'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::cmp', 'return_type' => self::TYPE_INT, 'min_args' => 1, 'max_args' => 1],
'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::abs', 'return_type' => self::TYPE_BIGINT, 'min_args' => 0, 'max_args' => 0],
'gcd' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::gcd', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1],
'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toString', 'return_type' => self::TYPE_STR, 'min_args' => 0, 'max_args' => 0],
'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0],
'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0],
],
self::TYPE_DECIMAL => [
'add' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::add', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1],
'sub' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::sub', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1],
'mul' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::mul', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1],
'div' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::div', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1],
'mod' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::mod', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1],
'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::neg', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0],
'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::cmp', 'return_type' => self::TYPE_INT, 'min_args' => 1, 'max_args' => 1],
'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::abs', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0],
'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::toString', 'return_type' => self::TYPE_STR, 'min_args' => 0, 'max_args' => 0],
'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0],
'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0],
],
self::TYPE_BIGFLOAT => [
'add' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::add', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1],
'sub' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::sub', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1],
'mul' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::mul', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1],
'div' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::div', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1],
'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::neg', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0],
'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::cmp', 'return_type' => self::TYPE_INT, 'min_args' => 1, 'max_args' => 1],
'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::abs', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0],
'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::toString', 'return_type' => self::TYPE_STR, 'min_args' => 0, 'max_args' => 0],
'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0],
'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0],
],
];
private const array MUTATING_HANDLERS = ['direct_method_mutate', 'php_fn_ref'];
private const array TYPE_SEARCH_ORDER = [self::TYPE_STR, self::TYPE_ARRAY, self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL, self::TYPE_STREAM];
private const array TYPE_SEARCH_ORDER = [self::TYPE_STR, self::TYPE_ARRAY, self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL, self::TYPE_STREAM, self::TYPE_BIGINT, self::TYPE_DECIMAL, self::TYPE_BIGFLOAT];
protected function detectUniversalMethodReturnType(string $type, string $method): ?string
{
@ -313,6 +353,9 @@ trait UniversalMethodCall
self::TYPE_STR => 'str',
self::TYPE_ARRAY => 'array',
self::TYPE_STREAM => 'stream',
self::TYPE_BIGINT => 'bigint',
self::TYPE_DECIMAL => 'decimal',
self::TYPE_BIGFLOAT => 'bigfloat',
];
protected function camelToSnake(string $name): string

@ -0,0 +1,100 @@
--TEST--
BigInt and Decimal operator overloading (+, -, *, /, %, **, <, >, <=, >=, ==, !=, <=>)
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
// === BigInt arithmetic operators ===
$a = std::bigInt(100);
$b = 200;
// BigInt + Int
$c = $a + $b;
echo $c->toString(); echo "\n";
// Int + BigInt
$d = 300 + $a;
echo $d->toString(); echo "\n";
// BigInt - Int
$e = $a - 30;
echo $e->toString(); echo "\n";
// BigInt * Int
$f = $a * 5;
echo $f->toString(); echo "\n";
// BigInt / Int
$g = $a / 3;
echo $g->toString(); echo "\n";
// BigInt % Int
$h = $a % 7;
echo $h->toString(); echo "\n";
// BigInt ** Int
$i = std::bigInt(2) ** 10;
echo $i->toString(); echo "\n";
// === BigInt comparison operators ===
echo (int)($a < $b); echo "\n";
echo (int)($a > $b); echo "\n";
echo (int)($a <= 100); echo "\n";
echo (int)($a >= 100); echo "\n";
echo (int)($a == 100); echo "\n";
echo (int)($a != 50); echo "\n";
// BigInt <=> (spaceship)
$sp1 = $a <=> $b;
echo (int)$sp1; echo "\n";
$sp2 = $a <=> 100;
echo (int)$sp2; echo "\n";
// === Decimal arithmetic operators ===
$dec = std::decimal("50.25");
// Decimal + Int
$j = $dec + 100;
echo $j->toString(); echo "\n";
// Decimal - Float
$k = $dec - 0.25;
echo $k->toString(); echo "\n";
// Decimal * Int
$l = $dec * 4;
echo $l->toString(); echo "\n";
// Decimal / Int
$m = $dec / 5;
echo $m->toString(); echo "\n";
// === Decimal comparison operators ===
echo (int)($dec > 10); echo "\n";
echo (int)($dec < 100); echo "\n";
echo (int)($dec == 50.25); echo "\n";
echo (int)($dec != 100); echo "\n";
// Decimal <=>
$sp3 = $dec <=> 60;
echo (int)$sp3; echo "\n";
}
?>
--EXPECT--
300
400
70
500
33
2
1024
1
0
1
1
1
1
-1
0
150.25
50.00
201.00
10.05
1
1
1
1
-1

@ -0,0 +1,78 @@
--TEST--
BigFloat operator overloading (+, -, *, /) and comparisons (<, >, <=, >=, ==, !=, <=>)
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
// === BigFloat arithmetic operators ===
$a = std::bigFloat(100.5);
$b = 200;
// BigFloat + Int
$c = $a + $b;
echo $c->toString(); echo "\n";
// Int + BigFloat
$d = 300 + $a;
echo $d->toString(); echo "\n";
// BigFloat - Int
$e = $a - 30;
echo $e->toString(); echo "\n";
// BigFloat * Int
$f = $a * 5;
echo $f->toString(); echo "\n";
// BigFloat / Int
$g = $a / 5;
echo $g->toString(); echo "\n";
// BigFloat + Float
$h = $a + 0.5;
echo $h->toString(); echo "\n";
// === BigFloat constructed from int ===
$i = std::bigFloat(42);
echo $i->toString(); echo "\n";
// === Unary minus ===
$neg = -$a;
echo $neg->toString(); echo "\n";
// === BigFloat comparison operators ===
echo (int)($a < $b); echo "\n";
echo (int)($a > $b); echo "\n";
echo (int)($a <= 100.5); echo "\n";
echo (int)($a >= 100.5); echo "\n";
echo (int)($a == 100.5); echo "\n";
echo (int)($a != 50); echo "\n";
// BigFloat <=> (spaceship)
$sp1 = $a <=> $b;
echo (int)$sp1; echo "\n";
$sp2 = $a <=> 100.5;
echo (int)$sp2; echo "\n";
// === Universal method calls ===
$j = std::bigFloat(3.14);
echo $j->toInt(); echo "\n";
echo $j->abs()->toString(); echo "\n";
}
?>
--EXPECT--
300.5
400.5
70.5
502.5
20.100000000000001
101
42
-100.5
1
0
1
1
1
1
-1
0
3
3.1400000000000001

@ -0,0 +1,27 @@
--TEST--
BigInt arithmetic operations
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$a = 12345678901234567890;
$b = 98765432109876543210;
echo "a+b="; echo $a->add($b)->toString(); echo "\n";
echo "b-a="; echo $b->sub($a)->toString(); echo "\n";
echo "a*b="; echo $a->mul($b)->toString(); echo "\n";
echo "b/a="; echo $b->div($a)->toString(); echo "\n";
echo "b%a="; echo $b->mod($a)->toString(); echo "\n";
echo "neg(a)="; echo $a->neg()->toString(); echo "\n";
echo "abs(neg(a))="; echo $a->neg()->abs()->toString(); echo "\n";
}
?>
--EXPECT--
a+b=111111111011111111100
b-a=86419753208641975320
a*b=1219326311370217952237463801111263526900
b/a=8
b%a=900000000090
neg(a)=-12345678901234567890
abs(neg(a))=12345678901234567890

@ -0,0 +1,20 @@
--TEST--
BigInt comparison
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$a = 12345678901234567890;
$b = 98765432109876543210;
// cmp returns -1 (a<b), 0 (a==b), 1 (a>b)
echo "cmp(a,b)="; echo $a->cmp($b); echo "\n";
echo "cmp(b,a)="; echo $b->cmp($a); echo "\n";
echo "cmp(a,a)="; echo $a->cmp($a); echo "\n";
}
?>
--EXPECT--
cmp(a,b)=-1
cmp(b,a)=1
cmp(a,a)=0

@ -0,0 +1,18 @@
--TEST--
BigInt literal parsing and output
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$a = 12345678901234567890;
echo $a->toString();
echo "\n";
$b = 99999999999999999999;
echo $b->toString();
}
?>
--EXPECT--
12345678901234567890
99999999999999999999

@ -0,0 +1,37 @@
--TEST--
BigInt mixed operations with Int
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$a = 12345678901234567890;
// BigInt + Int
$c = $a->add(100);
echo $c->toString(); echo "\n";
// BigInt * Int
$d = $a->mul(2);
echo $d->toString(); echo "\n";
// BigInt - Int
$e = $a->sub(7890);
echo $e->toString(); echo "\n";
// BigInt / Int
$f = $a->div(10);
echo $f->toString(); echo "\n";
// BigInt % Int
$g = $a->mod(1000000);
echo $g->toString(); echo "\n";
// BigInt cmp with Int
echo $a->cmp(100); echo "\n";
echo $a->cmp(99999999999999999999); echo "\n";
}
?>
--EXPECT--
12345678901234567990
24691357802469135780
12345678901234560000
1234567890123456789
567890
1
-1

@ -0,0 +1,50 @@
--TEST--
std::bigInt() and std::decimal() builtin functions
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
// std::bigInt from big literal (auto-detected BigInt → no-op pass-through)
$a = std::bigInt(12345678901234567890);
echo $a->toString(); echo "\n";
// std::bigInt from plain int
$b = std::bigInt(42);
echo $b->toString(); echo "\n";
// std::bigInt arithmetic
$c = $a->add($b);
echo $c->toString(); echo "\n";
// std::bigInt from string
$d = std::bigInt("99999999999999999999");
echo $d->toString(); echo "\n";
// std::decimal from int
$e = std::decimal(12345);
echo $e->toString(); echo "\n";
// std::decimal from string
$f = std::decimal("3.14159265358979323");
echo $f->toString(); echo "\n";
// std::decimal arithmetic
$g = $e->add($f);
echo $g->toString(); echo "\n";
// std::decimal from float literal (explicit, stays decimal)
$h = std::decimal(2.5);
echo $h->toString(); echo "\n";
}
?>
--EXPECT--
12345678901234567890
42
12345678901234567932
99999999999999999999
12345
3.14159265358979323
12348.14159265359
2.5
Loading…
Cancel
Save