feat(compiler): enhance big numeric type handling with improved conversions and validations

- Add support for precise BigFloat arithmetic beyond IEEE double precision
- Implement strict validation preventing unsupported operations like power operator on Decimal/BigFloat
- Add proper type conversion functions for BigInt, Decimal and BigFloat to int/float/bool
- Enhance documentation with accurate type specifications and usage guidelines
- Add comprehensive tests for boundary conditions and error handling
- Implement proper exception handling for arithmetic errors and invalid values
- Add support for mixed-type comparisons with explicit conversion requirements
- Fix precision issues in floating point representations and conversions
- Update type promotion rules to prevent unsafe implicit conversions between big types
pull/44/head
韩天峰 3 weeks ago
parent df43b2a1fd
commit f38b6cf0c2
  1. 49
      docs/HIGH_PRECISION_TYPES.md
  2. 40
      docs/NATIVE_TYPES.md
  3. 8
      phpunit/code/big-numeric/bigfloat-pow-operator.php
  4. 8
      phpunit/code/big-numeric/decimal-pow-operator.php
  5. 9
      phpunit/code/big-numeric/mixed-big-comparison.php
  6. 22
      phpunit/src/BigNumericValidationTest.php
  7. 5
      src/CompilerBase.php
  8. 6
      src/Optimizer/FuncCallOptimizer.php
  9. 20
      src/Parser/BinaryOpTrait.php
  10. 33
      src/Parser/TypeConversionTrait.php
  11. 10
      src/Parser/UnaryExpressionTrait.php
  12. 3
      tests/compiler/bigint/bitwise_shift.phpt
  13. 14
      tests/compiler/bigint/pow-right-bigint.phpt
  14. 6
      tests/compiler/bigint/toString_cast.phpt
  15. 22
      tests/compiler/bignumber/bigfloat-high-precision.phpt
  16. 2
      tests/compiler/bignumber/bigfloat_operators.phpt
  17. 65
      tests/compiler/bignumber/conversions-and-boundaries.phpt
  18. 28
      tests/compiler/decimal/precision-and-exception-boundaries.phpt

@ -1,6 +1,6 @@
# AOT 编译器高精度类型使用教程 # AOT 编译器高精度类型使用教程
本教程介绍 AOT 编译器中的三种高精度数值类型——**BigInt**(任意精度整数)、**Decimal**(任意精度十进制数)和 **BigFloat**(任意精度浮点数)——帮助你编写高精度、零开销的数值计算程序 本教程介绍 AOT 编译器中的三种高精度数值类型——**BigInt**(任意精度整数)、**Decimal**(50 位十进制数)和 **BigFloat**(256 bit 浮点数)
## 目录 ## 目录
@ -34,13 +34,13 @@ $a = 123456789012345678901234567890; // 30 位整数 → 被转为 float,精
$b = 0.1 + 0.2; // 0.30000000000000004 — 经典的浮点误差 $b = 0.1 + 0.2; // 0.30000000000000004 — 经典的浮点误差
``` ```
AOT 编译器提供了三种高精度类型,底层基于成熟的 C/C++ 数学库,编译为本地机器码,**零运行时开销** AOT 编译器提供了三种高精度类型,底层基于成熟的 C/C++ 数学库,并直接生成本地调用。这里的“零成本抽象”是指没有 PHP 方法查找和解释器分派开销;高精度运算本身仍需要数学库计算、内存分配和装箱
| 类型 | 底层库 | 特点 | | 类型 | 底层库 | 特点 |
|------|--------|------| |------|--------|------|
| BigInt | GMP (`libgmp`) | 任意精度整数,不会溢出 | | BigInt | GMP (`libgmp`) | 任意精度整数,不会溢出 |
| Decimal | libmpdec | 十进制小数,约 50 位有效数字,无二进制浮点误差 | | Decimal | libmpdec | 十进制小数,约 50 位有效数字,无二进制浮点误差 |
| BigFloat | MPFR (`libmpfr`) | 任意精度浮点数,可调精度 | | BigFloat | MPFR (`libmpfr`) | 默认 256 bit,字符串输出 64 位有效数字 |
--- ---
@ -89,7 +89,7 @@ $a = std::bigInt("1234567890123456789012345678901234567890"); // 40 位
$b = $a * 2; // 80 位,不会溢出 $b = $a * 2; // 80 位,不会溢出
``` ```
### Decimal — 任意精度十进制数 ### Decimal — 50 位十进制数
适用于金融计算等需要精确十进制表示的场景。`0.1 + 0.2` 精确等于 `0.3`,不存在二进制浮点误差。 适用于金融计算等需要精确十进制表示的场景。`0.1 + 0.2` 精确等于 `0.3`,不存在二进制浮点误差。
@ -99,9 +99,9 @@ $quantity = 3;
$total = $price * $quantity; // 59.97,精确 $total = $price * $quantity; // 59.97,精确
``` ```
### BigFloat — 任意精度浮点数 ### BigFloat — 256 bit 高精度浮点数
适用于科学计算等需要高精度浮点运算的场景。基于 MPFR,使用二进制浮点但精度远超 IEEE 754 double 适用于科学计算等需要高精度浮点运算的场景。基于 MPFR,当前默认精度固定为 256 bit,远高于 IEEE 754 double 的 53 bit
```php ```php
$pi = std::bigFloat("3.141592653589793238462643383279502884197"); $pi = std::bigFloat("3.141592653589793238462643383279502884197");
@ -152,7 +152,7 @@ $c = std::bigFloat(3.14); // → C++: php::Variant(new BigFloat(3.14))
### 5.1 标准运算符 ### 5.1 标准运算符
所有标准二元运算符都可以直接用于 Big* 类型 支持的运算符取决于具体类型:BigInt 支持 `+ - * / % **`,Decimal 支持 `+ - * / %`,BigFloat 支持 `+ - * /`
```php ```php
$a = std::bigInt(100); $a = std::bigInt(100);
@ -182,7 +182,7 @@ php::BigInt::pow(a, b) // BigInt 幂运算
### 5.2 与 int / float 混合运算 ### 5.2 与 int / float 混合运算
Big* 类型可以自由地与普通 int 和 float 混合运算,编译器自动进行类型提升: Big* 类型可以在安全范围内与普通 int/float 混合运算,编译器自动进行类型提升:
```php ```php
$a = std::bigInt(100); $a = std::bigInt(100);
@ -315,7 +315,7 @@ $a -= 1; // ✅ 代替 $a--
## 8. 通用方法调用 ## 8. 通用方法调用
Big* 类型支持通过 `$value->method()` 语法(通用方法/Universal Methods)调用方法。这些调用在编译时被翻译为对应的 C++ 静态函数,**零运行时开销** Big* 类型支持通过 `$value->method()` 语法(通用方法/Universal Methods)调用方法。这些调用在编译时直接翻译为对应的 C++ 静态函数,没有动态方法分派开销;数学库运算、结果分配和装箱成本仍然存在
### 8.1 BigInt 方法 ### 8.1 BigInt 方法
@ -343,7 +343,7 @@ if ($a->cmp(100) > 0) { /* $a > 100 */ }
// 类型转换方法 // 类型转换方法
echo $a->toString(); // 转字符串:"12345678901234567890" echo $a->toString(); // 转字符串:"12345678901234567890"
echo $a->toInt(); // 转 int(可能截断) echo $a->toInt(); // 转 int;超出 PHP int 范围时抛出 ArithmeticError
echo $a->toFloat(); // 转 float(可能丢精度) echo $a->toFloat(); // 转 float(可能丢精度)
``` ```
@ -433,12 +433,17 @@ $bf3 = std::bigFloat($big->toString());
// BigInt → 普通类型 // BigInt → 普通类型
$a = std::bigInt("99999999999999999999"); $a = std::bigInt("99999999999999999999");
$s = $a->toString(); // "99999999999999999999" $s = $a->toString(); // "99999999999999999999"
$i = $a->toInt(); // PHP_INT_MAX(超出范围时截断) $i = $a->toInt(); // 超出 PHP int 范围时抛出 ArithmeticError
$f = $a->toFloat(); // 1.0E+20(可能丢失精度) $f = $a->toFloat(); // 1.0E+20(可能丢失精度)
// 普通类型 → BigInt(通过编译期函数) // 普通类型 → BigInt(通过编译期函数)
$b = std::bigInt(42); // int → BigInt $b = std::bigInt(42); // int → BigInt
$c = std::bigInt("123456..."); // string → BigInt $c = std::bigInt("123456..."); // string → BigInt
// 强制转换和 PHP 转换函数会按数值转换,不会读取 Box resource id
$n = (int) std::decimal("12.75"); // 12
$x = floatval(std::bigInt("42")); // 42.0
$ok = boolval(std::bigFloat("0")); // false
``` ```
### 9.3 跨类型隐式混合的限制 ### 9.3 跨类型隐式混合的限制
@ -472,17 +477,14 @@ $c = $a + std::bigFloat($b->toString()); // ✅
## 10. 混合运算与类型提升 ## 10. 混合运算与类型提升
当 Big* 类型与普通 Int/Float 混合运算时,编译器按优先级确定运算类型: 当 Big* 类型与普通 Int/Float 混合运算时,编译器只执行不会改变数值模型的安全提升。
```
BigFloat > Decimal > BigInt > Float > Int
```
**规则**: **规则**:
1. 若任一操作数是 Var(非原生类型),则全部转为 Var,使用 ZendVM 运行时运算 1. 若任一操作数是 Var(非原生类型),则全部转为 Var,使用 ZendVM 运行时运算
2. 若两操作数均为 Int/Float,则 Float 优先(Int → Float) 2. 若两操作数均为 Int/Float,则 Float 优先(Int → Float)
3. 若任一操作数为 Big* 类型,则另一操作数自动提升为同类型(Int → BigInt 等) 3. BigInt 可安全提升 Int;Decimal 可提升 Int 和保留源码文本的 Float 字面量;BigFloat 可提升 Int/Float
4. 不同 Big* 类型之间,以及 BigInt 与 Float 之间,不进行隐式转换
```php ```php
// 类型提升示例 // 类型提升示例
@ -571,11 +573,20 @@ $c = $a + $b; // ❌ 编译错误
$c = $a + std::bigFloat($b->toString()); // ✅ $c = $a + std::bigFloat($b->toString()); // ✅
``` ```
### 12.6 不能在普通 PHP 解释器中运行 该限制同样适用于比较运算。比较前必须把两边显式转换为同一种 Big* 类型,避免编译为错误的底层资源类型。
### 12.6 边界和异常
- BigInt 负数右移采用算术右移,例如 `std::bigInt("-3") >> 1` 得到 `-2`
- 负数 bit index、负数 `popCount()`、过大的幂指数会抛出 `ValueError`
- 除零抛出 `DivisionByZeroError`;转为 PHP int 时超出范围抛出 `ArithmeticError`
- BigFloat 的绝对值指数超过 10000 时,`toString()` 自动使用科学计数法,避免构造超大字符串。
### 12.7 不能在普通 PHP 解释器中运行
Big* 类型是 AOT 编译器的专有特性,依赖编译期代码生成和 C++ 底层库。源码不能被 `php` 命令直接解释执行。 Big* 类型是 AOT 编译器的专有特性,依赖编译期代码生成和 C++ 底层库。源码不能被 `php` 命令直接解释执行。
### 12.7 启用 `use native_types` ### 12.8 启用 `use native_types`
忘记添加 `use native_types` 会导致 Big* 变量被当作 Var(通用类型),失去原生类型的大部分性能优势。 忘记添加 `use native_types` 会导致 Big* 变量被当作 Var(通用类型),失去原生类型的大部分性能优势。

@ -11,8 +11,8 @@
### 高精度数值类型 ### 高精度数值类型
4. ✅ `std::bigInt` - 任意精度整数 (基于 GMP `mpz_class`) 4. ✅ `std::bigInt` - 任意精度整数 (基于 GMP `mpz_class`)
5. ✅ `std::decimal` - 任意精度十进制数 (基于 libmpdec, ~50 位有效数字) 5. ✅ `std::decimal` - 50 位十进制数 (基于 libmpdec)
6. ✅ `std::bigFloat` - 任意精度浮点数 (基于 MPFR) 6. ✅ `std::bigFloat` - 256 bit 高精度浮点数 (基于 MPFR,输出 64 位有效数字)
--- ---
@ -287,7 +287,7 @@ $g = std::bigFloat("3.14159265358979323846");
### 算术运算符 ### 算术运算符
所有标准二元运算符均已重载:`+`、`-`、`*`、`/`、`%`(取模)、`**`(幂运算)。编译器将其映射为静态方法调用。 BigInt 支持 `+`、`-`、`*`、`/`、`%` 和 `**`;Decimal 支持除 `**` 外的前五项;BigFloat 支持 `+`、`-`、`*`、`/`。编译器将它们映射为静态方法调用。
```php ```php
$a = std::bigInt(100); $a = std::bigInt(100);
@ -304,7 +304,7 @@ $pow = $a ** 3; // → php::BigInt::pow($a, 3)
$neg = -$a; // → php::BigInt::neg($a) $neg = -$a; // → php::BigInt::neg($a)
``` ```
**类型提升**:当 Big* 类型与 Int/Float 混合运算时,Int/Float 自动提升为对应的高精度类型。详见下文"二元运算类型提升规则" **类型提升**:Big* 可以和安全的普通标量混合运算;不同 Big* 类型之间不得隐式混合,必须先显式转换。详见下文“二元运算类型提升规则”
**BigInt 除法**:`BigInt / BigInt` 在 `parseBinaryOp` 中返回 BigInt(整数除法,同 PHP int 语义)。若需要高精度除法,应先将操作数转为 Decimal 或使用 `BigInt::div` 的 Decimal 结果。 **BigInt 除法**:`BigInt / BigInt` 在 `parseBinaryOp` 中返回 BigInt(整数除法,同 PHP int 语义)。若需要高精度除法,应先将操作数转为 Decimal 或使用 `BigInt::div` 的 Decimal 结果。
@ -343,7 +343,7 @@ BigInt、Decimal、BigFloat 支持通过 `$value->method()` 语法调用一系
| `gcd($x)` | 1 | BigInt | `BigInt::gcd()` | 最大公约数 | | `gcd($x)` | 1 | BigInt | `BigInt::gcd()` | 最大公约数 |
| `cmp($x)` | 1 | Int | `BigInt::cmp()` | 比较 | | `cmp($x)` | 1 | Int | `BigInt::cmp()` | 比较 |
| `toString()` | 0 | Str | `BigInt::toString()` | 转字符串 | | `toString()` | 0 | Str | `BigInt::toString()` | 转字符串 |
| `toInt()` | 0 | Int | `BigInt::toInt()` | 转整数 (可能截断) | | `toInt()` | 0 | Int | `BigInt::toInt()` | 转整数,越界抛出 ArithmeticError |
| `toFloat()` | 0 | Float | `BigInt::toFloat()` | 转浮点 (可能丢精度) | | `toFloat()` | 0 | Float | `BigInt::toFloat()` | 转浮点 (可能丢精度) |
```php ```php
@ -422,7 +422,7 @@ $bf = std::bigFloat(3.14);
$bf2 = std::bigFloat($big->toString()); $bf2 = std::bigFloat($big->toString());
``` ```
> **跨类型隐式转换限制**:BigFloat 与 BigInt/Decimal 之间不能隐式混合运算。编译器会报错提示使用 `std::bigFloat()` 显式转换。这是为了防止意外的精度损失 > **跨类型隐式转换限制**:BigInt、Decimal、BigFloat 之间不能隐式混合运算或比较。编译器会报错并要求先显式转换为同一类型,这是为了防止精度损失和底层 Box 类型误用
### C++ API 参考 ### C++ API 参考
@ -483,7 +483,7 @@ AOT 编译器在执行 `+`、`-`、`*`、`/`、`%` 等二元运算时,按以
``` ```
BigFloat / Decimal / BigInt 参与 BigFloat / Decimal / BigInt 参与
提升到最高精度类型进行计算 仅安全提升 Int/Float;不同 Big* 类型要求显式转换
↓ 未命中 ↓ 未命中
任一边为 Var 任一边为 Var
@ -527,22 +527,22 @@ $f = $d + $e; // Int + Int → int64_t 加法
> **注意**:原生类型变量在运算中**不会改变自身类型**。如 `Int += Float` 在 C++ 中执行 `int64_t += double`,结果截断为 int64_t,与 PHP 行为不同(PHP 中变量会变为 float)。这是 `use native_types` 有意为之的语义。 > **注意**:原生类型变量在运算中**不会改变自身类型**。如 `Int += Float` 在 C++ 中执行 `int64_t += double`,结果截断为 int64_t,与 PHP 行为不同(PHP 中变量会变为 float)。这是 `use native_types` 有意为之的语义。
### 规则三:大数类型精度提升 ### 规则三:高精度类型的安全提升
当运算数中包含 `BigInt`、`Decimal` 或 `BigFloat` 时,按精度层级提升:`BigFloat > Decimal > BigInt > Float > Int` 当运算数中包含 `BigInt`、`Decimal` 或 `BigFloat` 时,只对普通标量执行明确且安全的提升。不同 Big* 类型不会按所谓“精度层级”自动转换,因为三者的数值模型不同
| 左操作数 | 右操作数 | 结果类型 | | 左操作数 | 右操作数 | 结果类型 |
|---------|---------|---------| |---------|---------|---------|
| BigInt | BigInt | BigInt(除法 `/` 得 Decimal) | | BigInt | BigInt | BigInt(`/` 为截断整数除法) |
| BigInt | Decimal | Decimal | | BigInt | Decimal | 编译错误,需显式转换 |
| Decimal | Decimal | Decimal | | Decimal | Decimal | Decimal |
| BigFloat | BigInt | BigFloat | | BigFloat | BigInt | 编译错误,需显式转换 |
| BigFloat | Decimal | BigFloat | | BigFloat | Decimal | 编译错误,需显式转换 |
| BigFloat | BigFloat | BigFloat | | BigFloat | BigFloat | BigFloat |
| BigInt | Int | BigInt | | BigInt | Int | BigInt |
| BigInt | Float | Decimal | | BigInt | Float | 编译错误 |
| Decimal | Int | Decimal | | Decimal | Int | Decimal |
| Decimal | Float | Decimal | | Decimal | Float | Decimal(float 字面量按源码文本转换;变量需显式转换) |
| BigFloat | Int | BigFloat | | BigFloat | Int | BigFloat |
| BigFloat | Float | BigFloat | | BigFloat | Float | BigFloat |
@ -551,13 +551,13 @@ $f = $d + $e; // Int + Int → int64_t 加法
| | Int | Float | Var | BigInt | Decimal | BigFloat | | | Int | Float | Var | BigInt | Decimal | BigFloat |
|------|-----|-------|-----|--------|---------|----------| |------|-----|-------|-----|--------|---------|----------|
| **Int** | Int | Float | Var | BigInt | Decimal | BigFloat | | **Int** | Int | Float | Var | BigInt | Decimal | BigFloat |
| **Float** | Float | Float | Var | Decimal | Decimal | BigFloat | | **Float** | Float | Float | Var | 错误 | Decimal* | BigFloat |
| **Var** | Var | Var | Var | Var | Var | Var | | **Var** | Var | Var | Var | Var | Var | Var |
| **BigInt** | BigInt | Decimal | Var | BigInt | Decimal | BigFloat | | **BigInt** | BigInt | 错误 | Var | BigInt | 错误 | 错误 |
| **Decimal** | Decimal | Decimal | Var | Decimal | Decimal | BigFloat | | **Decimal** | Decimal | Decimal* | Var | 错误 | Decimal | 错误 |
| **BigFloat** | BigFloat | BigFloat | Var | BigFloat | BigFloat | BigFloat | | **BigFloat** | BigFloat | BigFloat | Var | 错误 | 错误 | BigFloat |
> **说明**:Var 行/列全部为 Var,因为 Var 主导规则优先级最高(除 Big* 类型外)。Big* 类型参与时,Var 退让,以高精度类型为准 > `Decimal*`:只允许编译器能够保留原始文本的 float 字面量;float 变量必须先显式转换。Var 行/列仍使用 ZendVM 运行时语义
### 复合赋值运算符 ### 复合赋值运算符

@ -0,0 +1,8 @@
<?php
use native_types;
function main(): void
{
$bigfloat = std::bigFloat('2');
echo $bigfloat ** 3;
}

@ -0,0 +1,8 @@
<?php
use native_types;
function main(): void
{
$decimal = std::decimal('2');
echo $decimal ** 3;
}

@ -0,0 +1,9 @@
<?php
use native_types;
function main(): void
{
$bigint = std::bigInt('2');
$decimal = std::decimal('2');
var_dump($bigint == $decimal);
}

@ -0,0 +1,22 @@
<?php
class BigNumericValidationTest extends \BaseTest
{
public function testDecimalPowerOperatorIsRejected(): void
{
$this->exec("Operator '**' is not supported for Decimal or BigFloat", 'big-numeric/decimal-pow-operator.php');
}
public function testBigFloatPowerOperatorIsRejected(): void
{
$this->exec("Operator '**' is not supported for Decimal or BigFloat", 'big-numeric/bigfloat-pow-operator.php');
}
public function testDifferentBigTypesCannotBeComparedImplicitly(): void
{
$this->exec(
'Cannot compare different Big* types implicitly',
'big-numeric/mixed-big-comparison.php'
);
}
}

@ -3574,7 +3574,10 @@ class CompilerBase implements PropertyAccessContext
protected function parseCastDouble(mixed $expr): string protected function parseCastDouble(mixed $expr): string
{ {
$this->assertExprCanBeUsedAsValue($expr->expr, 'cast operand'); $this->assertExprCanBeUsedAsValue($expr->expr, 'cast operand');
return $this->convertFloatExpr($this->parseIdentifier($expr->expr)); return $this->convertFloatExpr(
$this->parseIdentifier($expr->expr),
$this->detectTypeOfExpr($expr->expr)
);
} }
protected function detectFuncCallReturnType(string $name): string protected function detectFuncCallReturnType(string $name): string

@ -484,9 +484,9 @@ trait FuncCallOptimizer
} }
return match ($convType) { return match ($convType) {
self::ARG_TYPE_INT => $this->convertIntExpr($parsed), self::ARG_TYPE_INT => $this->convertIntExpr($parsed, $type),
self::ARG_TYPE_FLOAT => $this->convertFloatExpr($parsed), self::ARG_TYPE_FLOAT => $this->convertFloatExpr($parsed, $type),
self::ARG_TYPE_BOOL => $this->convertBoolExpr($parsed), self::ARG_TYPE_BOOL => $this->convertBoolExpr($parsed, $type),
default => $parsed, default => $parsed,
}; };
} }

@ -332,10 +332,17 @@ trait BinaryOpTrait
$this->assertExprCanBeUsedAsValue($expr->left, 'binary operand'); $this->assertExprCanBeUsedAsValue($expr->left, 'binary operand');
$this->assertExprCanBeUsedAsValue($expr->right, 'binary operand'); $this->assertExprCanBeUsedAsValue($expr->right, 'binary operand');
$leftType = $this->detectTypeOfExpr($expr->left); $leftType = $this->detectTypeOfExpr($expr->left);
if ($leftType === Type::BIGINT) { $rightType = $this->detectTypeOfExpr($expr->right);
if ($leftType === Type::DECIMAL || $rightType === Type::DECIMAL
|| $leftType === Type::BIGFLOAT || $rightType === Type::BIGFLOAT) {
$this->fatalError($expr, "Operator '**' is not supported for Decimal or BigFloat; use pow() where supported");
}
if ($leftType === Type::BIGINT || $rightType === Type::BIGINT) {
$leftExpr = $this->parseOrderedOperand($expr->left, false); $leftExpr = $this->parseOrderedOperand($expr->left, false);
$rightExpr = $this->parseOrderedOperand($expr->right, false); $rightExpr = $this->parseOrderedOperand($expr->right, false);
$rightType = $this->detectTypeOfExpr($expr->right); if ($leftType !== Type::BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
}
if ($rightType !== Type::BIGINT) { if ($rightType !== Type::BIGINT) {
$rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType);
} }
@ -518,6 +525,15 @@ trait BinaryOpTrait
$leftType = $this->detectTypeOfExpr($expr->left); $leftType = $this->detectTypeOfExpr($expr->left);
$rightType = $this->detectTypeOfExpr($expr->right); $rightType = $this->detectTypeOfExpr($expr->right);
$bigTypes = [Type::BIGINT, Type::DECIMAL, Type::BIGFLOAT];
if (in_array($leftType, $bigTypes, true) && in_array($rightType, $bigTypes, true)
&& $leftType !== $rightType) {
$this->fatalError(
$expr,
'Cannot compare different Big* types implicitly; convert both operands to the same type explicitly'
);
}
if ($leftType === Type::BIGFLOAT || $rightType === Type::BIGFLOAT) { if ($leftType === Type::BIGFLOAT || $rightType === Type::BIGFLOAT) {
$leftExpr = $this->parseOrderedOperand($expr->left, false); $leftExpr = $this->parseOrderedOperand($expr->left, false);
$rightExpr = $this->parseOrderedOperand($expr->right, false); $rightExpr = $this->parseOrderedOperand($expr->right, false);

@ -32,8 +32,17 @@ trait TypeConversionTrait
return $this->convertStringExpr($expr); return $this->convertStringExpr($expr);
} }
protected function convertIntExpr(string $expr): string protected function convertIntExpr(string $expr, string $fromType = ''): string
{ {
$bigConversion = match ($fromType) {
Type::BIGINT => 'php::BigInt::toInt',
Type::BIGFLOAT => 'php::BigFloat::toInt',
Type::DECIMAL => 'php::Decimal::toInt',
default => null,
};
if ($bigConversion !== null) {
return $bigConversion . '(' . $expr . ')';
}
if (!$this->isClosedExpr($expr, 'php::toInt')) { if (!$this->isClosedExpr($expr, 'php::toInt')) {
return 'php::toInt(' . $expr . ')'; return 'php::toInt(' . $expr . ')';
} }
@ -41,8 +50,17 @@ trait TypeConversionTrait
return $expr; return $expr;
} }
protected function convertFloatExpr(string $expr): string protected function convertFloatExpr(string $expr, string $fromType = ''): string
{ {
$bigConversion = match ($fromType) {
Type::BIGINT => 'php::BigInt::toFloat',
Type::BIGFLOAT => 'php::BigFloat::toFloat',
Type::DECIMAL => 'php::Decimal::toFloat',
default => null,
};
if ($bigConversion !== null) {
return $bigConversion . '(' . $expr . ')';
}
if (!$this->isClosedExpr($expr, 'php::toFloat')) { if (!$this->isClosedExpr($expr, 'php::toFloat')) {
return 'php::toFloat(' . $expr . ')'; return 'php::toFloat(' . $expr . ')';
} }
@ -142,8 +160,17 @@ trait TypeConversionTrait
return $expr; return $expr;
} }
protected function convertBoolExpr(string $expr): string protected function convertBoolExpr(string $expr, string $fromType = ''): string
{ {
$bigConversion = match ($fromType) {
Type::BIGINT => 'php::BigInt::toBool',
Type::BIGFLOAT => 'php::BigFloat::toBool',
Type::DECIMAL => 'php::Decimal::toBool',
default => null,
};
if ($bigConversion !== null) {
return $bigConversion . '(' . $expr . ')';
}
if (!$this->isClosedExpr($expr, 'php::toBool')) { if (!$this->isClosedExpr($expr, 'php::toBool')) {
return 'php::toBool(' . $expr . ')'; return 'php::toBool(' . $expr . ')';
} }

@ -34,7 +34,10 @@ trait UnaryExpressionTrait
protected function parseCastInt(Expr\Cast\Int_ $node): string protected function parseCastInt(Expr\Cast\Int_ $node): string
{ {
$this->assertExprCanBeUsedAsValue($node->expr, 'cast operand'); $this->assertExprCanBeUsedAsValue($node->expr, 'cast operand');
return $this->convertIntExpr($this->parseExprAsValue($node->expr)); return $this->convertIntExpr(
$this->parseExprAsValue($node->expr),
$this->detectTypeOfExpr($node->expr)
);
} }
protected function parseCastString(Expr\Cast\String_ $node): string protected function parseCastString(Expr\Cast\String_ $node): string
@ -49,7 +52,10 @@ trait UnaryExpressionTrait
protected function parseCastBool(Expr\Cast\Bool_ $node): string protected function parseCastBool(Expr\Cast\Bool_ $node): string
{ {
$this->assertExprCanBeUsedAsValue($node->expr, 'cast operand'); $this->assertExprCanBeUsedAsValue($node->expr, 'cast operand');
return $this->convertBoolExpr($this->parseExprAsValue($node->expr)); return $this->convertBoolExpr(
$this->parseExprAsValue($node->expr),
$this->detectTypeOfExpr($node->expr)
);
} }
protected function parseCastObject(Expr\Cast\Object_ $node): string protected function parseCastObject(Expr\Cast\Object_ $node): string

@ -20,6 +20,8 @@ function main(): void {
// Right shift truncates (integer division by 2^n) // Right shift truncates (integer division by 2^n)
$c = std::bigInt("15"); $c = std::bigInt("15");
echo ($c >> 1)->toString(); echo "\n"; // 7 echo ($c >> 1)->toString(); echo "\n"; // 7
$negativeOdd = std::bigInt("-3");
echo ($negativeOdd >> 1)->toString(); echo "\n"; // -2, arithmetic shift
// Compound shift left // Compound shift left
$d = std::bigInt("1"); $d = std::bigInt("1");
@ -49,6 +51,7 @@ function main(): void {
64 64
16 16
7 7
-2
1024 1024
32 32
256 256

@ -0,0 +1,14 @@
--TEST--
BigInt exponentiation dispatches when BigInt is the right operand
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$exponent = std::bigInt("10");
echo (2 ** $exponent)->toString(), "\n";
}
?>
--EXPECT--
1024

@ -29,9 +29,9 @@ function main(): void {
string(20) "12345678901234567890" string(20) "12345678901234567890"
string(20) "12345678901234567890" string(20) "12345678901234567890"
12345678901234567890 12345678901234567890
string(18) "3.1415926535897931" string(17) "3.141592653589793"
string(18) "3.1415926535897931" string(17) "3.141592653589793"
3.1415926535897931 3.141592653589793
string(11) "0.123456789" string(11) "0.123456789"
string(11) "0.123456789" string(11) "0.123456789"
0.123456789 0.123456789

@ -0,0 +1,22 @@
--TEST--
BigFloat arithmetic retains precision beyond IEEE double
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$large = std::bigFloat("1000000000000000000000000000000");
$one = std::bigFloat("1");
echo (($large + $one) - $large)->toString(), "\n";
try {
$unused = std::bigFloat("not-a-number");
} catch (ValueError $e) {
echo "invalid bigfloat caught\n";
}
}
?>
--EXPECT--
1
invalid bigfloat caught

@ -62,7 +62,7 @@ function main(): void {
400.5 400.5
70.5 70.5
502.5 502.5
20.100000000000001 20.1
101 101
42 42
-100.5 -100.5

@ -0,0 +1,65 @@
--TEST--
Big numeric casts, conversion functions, and runtime boundaries
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$bigint = std::bigInt("42");
var_dump((int) $bigint, (float) $bigint, (bool) $bigint);
var_dump(intval($bigint), floatval($bigint), boolval(std::bigInt("0")));
$decimal = std::decimal("-12.75");
var_dump((int) $decimal, (float) $decimal, (bool) $decimal, boolval(std::decimal("0.00")));
$bigfloat = std::bigFloat("3.75");
var_dump((int) $bigfloat, (float) $bigfloat, (bool) $bigfloat, boolval(std::bigFloat("0")));
try {
$bigintRangeValue = (int) std::bigInt("9223372036854775808");
} catch (ArithmeticError $e) {
echo "bigint range caught\n";
}
try {
$invalidBigint = std::bigInt("not-an-integer");
} catch (ValueError $e) {
echo "invalid bigint caught\n";
}
try {
$bigfloatDivision = std::bigFloat("1") / 0;
} catch (DivisionByZeroError $e) {
echo "bigfloat division caught\n";
}
try {
$bigfloatRangeValue = (int) std::bigFloat("1e100");
} catch (ArithmeticError $e) {
echo "bigfloat range caught\n";
}
echo std::bigFloat("1e1000001")->toString(), "\n";
}
?>
--EXPECT--
int(42)
float(42)
bool(true)
int(42)
float(42)
bool(false)
int(-12)
float(-12.75)
bool(true)
bool(false)
int(3)
float(3.75)
bool(true)
bool(false)
bigint range caught
invalid bigint caught
bigfloat division caught
bigfloat range caught
1E1000001

@ -0,0 +1,28 @@
--TEST--
Decimal keeps 50 digits and translates native arithmetic errors
--FILE--
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$large = std::decimal("1234567890123456789012345678901234567890123456789");
echo ($large + 1)->toString(), "\n";
try {
$unused = std::decimal("1") / 0;
} catch (DivisionByZeroError $e) {
echo "division by zero caught\n";
}
try {
$unused = std::decimal("not-a-decimal");
} catch (ValueError $e) {
echo "invalid decimal caught\n";
}
}
?>
--EXPECT--
1234567890123456789012345678901234567890123456790
division by zero caught
invalid decimal caught
Loading…
Cancel
Save