Skip to content

Compute the numeric operators on NumberSet - #6955

Merged
ondrejmirtes merged 4 commits into
2.3.xfrom
number-set
Nov 4, 2026
Merged

ondrejmirtes merged 4 commits into
2.3.xfrom
number-set

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Adds Type::toNumberSet(): ?NumberSet, which describes the numbers a type holds as integer intervals, float intervals and a NAN flag. The trait default returns null; it is implemented on IntegerType, ConstantIntegerType, IntegerRangeType, FloatType, ConstantFloatType, FloatRangeType and UnionType.

The arithmetic operators +, -, *, /, << and >> now run as NumberSet methods, such as NumberSet::add(NumberSet $right, PhpVersion $phpVersion), instead of the instanceof chains in OperatorTypeHelper. Each pair of members is computed on its own: two integer intervals use the integer rules, and every other pair uses the float interval corners.

Ranges are kept as intervals and are never expanded value by value. Above CALCULATE_SCALARS_LIMIT, a set collapses to its hull.

Two commits:

  1. Describe the numbers a type holds with Type::toNumberSet(). The interval readers in OperatorTypeHelper and ExponentiateHelper now read the set.
  2. Compute the numeric operators on NumberSet. This removes integerRangeMath, floatRangeMath, floatIntervalMath and the related helpers. Float members of integer unions are now computed instead of passed through: int<1, 5> + (1.5|2) is float<2.5, 6.5>|int<3, 7>.

Changed expectations, each narrower and still sound:

  • int<5, max> / -2 is int<min, -3> (was int<min, -2>, which was wrong: 6 / -2 = -3 is the first exact integer result)
  • 1 / $int is -1|1|float
  • $float * 0 is 0.0|NAN, and 0 * $intOrFloat is 0|0.0|NAN
  • rand() * 0.1 is float<0.0, max>
  • in for ($xi = $x0; $xi < $xn; $xi += 0.1), $xn is int<2, max>

Both commits include the Turbo C++ mirrors (NumberSet.cpp, OperatorTypeHelper.cpp). The test suite passes with and without the extension. A local operator fuzzer that compares the inferred types with runtime results on PHP 7.4 to 8.6 passes, and self-analysis output is identical with Turbo on and off on PHP 8.4 and 8.5.

The operator helpers read integer and float intervals from the members of a
type through a chain of instanceof checks. A NumberSet now carries the
integer intervals, the float intervals and NAN of a type, so they read
those instead.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b
+, -, *, /, << and >> now run as NumberSet methods on the operands'
toNumberSet(), pair by pair: integer pairs keep the integer rules,
every other pair uses the float interval corners. This replaces the
interval and float-range special cases in OperatorTypeHelper, and
float members of integer unions are no longer passed through.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b
The == narrowing follows the analysed PHP version: PHP 7 compares a
non-numeric string with a number as a number, has no Stringable, and
BcMath\Number compares as its number since PHP 8.4. The nsrt
comparisons now run inside PHP_VERSION_ID guards, so their results no
longer depend on the PHP version running the tests.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b
@ondrejmirtes
ondrejmirtes merged commit ede890e into 2.3.x Nov 4, 2026
532 of 538 checks passed
@ondrejmirtes
ondrejmirtes deleted the number-set branch November 4, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant