feat(compiler): 添加对 Big* 类型的字符串转换和数学函数优化支持

- 在 echo 表达式中实现 BigInt、BigFloat 和 Decimal 类型的自动 toString 转换
- 为 abs、pow、sqrt、floor、ceil、round 数学函数添加 Big* 类型返回值推断
- 优化二进制连接操作中的类型转换逻辑,支持 Big* 类型到字符串的转换
- 为 (string) 强制转换、strval() 函数和 floor/ceil/round 函数添加 Big* 类型支持
- 新增测试用例验证 Big* 类型的数学函数优化功能
- 添加 Decimal 类型的精确计算和舍入处理示例
pull/1/head
韩天峰 3 months ago
parent 5129bab900
commit c604fc09ab
  1. 10
      examples/decimal/decimal.php
  2. 63
      src/Php/CompilerBase.php
  3. 62
      src/Php/FuncCallOptimizer.php
  4. 33
      tests/aot/bigint/math_func_opt.phpt
  5. 37
      tests/aot/bigint/toString_cast.phpt
  6. 17
      tests/aot/decimal/floor-02.phpt
  7. 25
      tests/aot/decimal/round-02.phpt

@ -0,0 +1,10 @@
<?php
use decimal_types;
function main()
{
$a = 0.1 + 0.2;
$b = 1000;
echo $a, $b;
}

@ -1781,7 +1781,16 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($expr instanceof Expr\Assign) {
$this->fatalError($expr, 'Cannot echo assign expression');
} else {
$lines[] = 'php::echo(' . $this->parseExpr($expr) . ');';
$type = $this->detectTypeOfExpr($expr);
$parsed = $this->parseExpr($expr);
if ($type === self::TYPE_BIGINT) {
$parsed = 'php::BigInt::toString(' . $parsed . ')';
} elseif ($type === self::TYPE_BIGFLOAT) {
$parsed = 'php::BigFloat::toString(' . $parsed . ')';
} elseif ($type === self::TYPE_DECIMAL) {
$parsed = 'php::Decimal::toString(' . $parsed . ')';
}
$lines[] = 'php::echo(' . $parsed . ');';
}
}
@ -2460,6 +2469,25 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Expr_FuncCall':
if ($this->isNameExpr($expr->name)) {
$name = $this->parseIdentifier($expr->name);
// Math function optimization: propagate Big* return types
if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) && !empty($expr->args)) {
$argType = $this->detectTypeOfExpr($expr->args[0]->value);
if (
$argType === self::TYPE_BIGINT
&& in_array($name, ['abs', 'pow', 'sqrt'], true)
) {
return self::TYPE_BIGINT;
}
if (
$argType === self::TYPE_DECIMAL
&& in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true)
) {
return self::TYPE_DECIMAL;
}
if ($argType === self::TYPE_BIGFLOAT && $name === 'abs') {
return self::TYPE_BIGFLOAT;
}
}
if (in_array($name, self::STREAM_FUNCTIONS) || $name === 'stream_cast') {
return self::TYPE_STREAM;
}
@ -2976,15 +3004,29 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBinaryOpConcat(Expr\BinaryOp\Concat $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
$left = $this->parseExpr($expr->left);
$right = $this->parseExpr($expr->right);
$leftType = $this->detectTypeOfExpr($expr->left);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($leftType === self::TYPE_VOID or $rightType === self::TYPE_VOID) {
$this->fatalError($expr, 'Cannot concat void');
}
return Symbol::concat() . '(' . $this->convertStringExpr($left) . ', ' . $this->convertStringExpr($right) . ')';
return Symbol::concat() . '(' . $this->convertExprToStringByType($left, $leftType) . ', ' . $this->convertExprToStringByType($right, $rightType) . ')';
}
protected function convertExprToStringByType(string $expr, $type): string
{
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::toString(' . $expr . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::toString(' . $expr . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::toString(' . $expr . ')';
}
return $this->convertStringExpr($expr);
}
protected function parseFor(Node\Stmt\For_ $v): string
@ -4348,7 +4390,18 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseCastString(Expr\Cast\String_ $node): string
{
return $this->convertStringExpr($this->parseExpr($node->expr));
$type = $this->detectTypeOfExpr($node->expr);
$expr = $this->parseExpr($node->expr);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::toString(' . $expr . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::toString(' . $expr . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::toString(' . $expr . ')';
}
return $this->convertStringExpr($expr);
}
protected function parseCastBool(Expr\Cast\Bool_ $node): string

@ -69,7 +69,19 @@ trait FuncCallOptimizer
case 'boolval':
return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value));
case 'strval':
return $this->convertStringExpr($this->parseExpr($expr->args[0]->value));
$arg = $expr->args[0]->value;
$type = $this->detectTypeOfExpr($arg);
$parsed = $this->parseExpr($arg);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::toString(' . $parsed . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::toString(' . $parsed . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::toString(' . $parsed . ')';
}
return $this->convertStringExpr($parsed);
default:
break;
}
@ -91,11 +103,59 @@ trait FuncCallOptimizer
}
}
if ($name === 'abs') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::abs(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::abs(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::abs(' . $this->parseExpr($expr->args[0]->value) . ')';
}
return 'php::math::abs(' . $getArg(0) . ')';
}
if ($name === 'pow') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::pow(' . $this->parseExpr($expr->args[0]->value) . ', ' . $this->parseExpr($expr->args[1]->value) . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::pow(' . $this->parseExpr($expr->args[0]->value) . ', ' . $this->parseExpr($expr->args[1]->value) . ')';
}
return 'php::math::pow(' . $getArg(0) . ', ' . $getArg(1) . ')';
}
if ($name === 'sqrt') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
}
}
if ($name === 'floor') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::floor(' . $this->parseExpr($expr->args[0]->value) . ')';
}
}
if ($name === 'ceil') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::ceil(' . $this->parseExpr($expr->args[0]->value) . ')';
}
}
if ($name === 'round') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
$arg0 = $this->parseExpr($expr->args[0]->value);
if (count($expr->args) >= 2) {
return 'php::Decimal::round(' . $arg0 . ', ' . $this->parseExpr($expr->args[1]->value) . ')';
}
return 'php::Decimal::round(' . $arg0 . ')';
}
}
if ($name === 'strlen') {
return 'php::fn::strlen(' . $getArg(0) . ')';
}

@ -0,0 +1,33 @@
--TEST--
Big* types: math function optimization (abs/pow/sqrt/floor/ceil/round)
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
// BigInt
$a = std::bigInt("100");
$a1 = abs($a);
$a2 = pow($a, 2);
$a3 = sqrt($a);
// Decimal
$b = std::decimal("3.14");
$b1 = abs($b);
$b2 = pow($b, 3);
$b3 = sqrt($b);
$b4 = floor($b);
$b5 = ceil($b);
$b6 = round($b);
$b7 = round(std::decimal("3.14159"), 2);
// BigFloat
$c = std::bigFloat("-2.5");
$c1 = abs($c);
echo "OK\n";
}
?>
--EXPECT--
OK

@ -0,0 +1,37 @@
--TEST--
Big* types: (string) cast, strval() and echo → toString()
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
// BigInt
$a = std::bigInt("12345678901234567890");
var_dump((string) $a);
var_dump(strval($a));
echo $a; echo "\n";
// BigFloat
$b = std::bigFloat("3.141592653589793");
var_dump((string) $b);
var_dump(strval($b));
echo $b; echo "\n";
// Decimal
$c = std::decimal("0.123456789");
var_dump((string) $c);
var_dump(strval($c));
echo $c; echo "\n";
}
?>
--EXPECT--
string(20) "12345678901234567890"
string(20) "12345678901234567890"
12345678901234567890
string(18) "3.1415926535897931"
string(18) "3.1415926535897931"
3.1415926535897931
string(11) "0.123456789"
string(11) "0.123456789"
0.123456789

@ -0,0 +1,17 @@
--TEST--
decimal: literal
--FILE--
<?php
use decimal_types;
function main()
{
require __DIR__ . '/../../../src/Assert.php';
$amount = 19.99;
$rate = 0.09;
$tax = $amount * $rate; // 1.7991 —— Decimal 精确计算
echo floor($tax * 100) / 100.0; // floor(179.91) / 100.0 = 1.79
}
?>
--EXPECT--
1.79

@ -0,0 +1,25 @@
--TEST--
Decimal: round
--FILE--
<?php
use decimal_types;
function main(): void {
$item1 = 19.99; // 商品A
$item2 = 29.99; // 商品B
$item3 = 39.99; // 商品C
$tax_rate = 0.08;
$subtotal = $item1 + $item2 + $item3;
$tax = $subtotal * $tax_rate;
$total = $subtotal + $tax;
echo "小计: " . round($subtotal, 2) . "\n"; // 89.97
echo "税额: " . round($tax, 4) . "\n"; // 7.1976
echo "总计: " . round($total, 2) . "\n"; // 97.17
}
?>
--EXPECT--
小计: 89.97
税额: 7.1976
总计: 97.17
Loading…
Cancel
Save