sebastianbergmann/comparator · error · ComparisonFailure

Failed asserting that two Number objects are equal.

Error message

Failed asserting that two Number objects are equal.

What it means

NumberComparator compares BcMath\Number values (arbitrary-precision decimals) within an optional delta. It throws a ComparisonFailure when the actual value falls outside expected ± delta after both sides are normalized to Number objects at the maximum scale of the two operands.

Solutions

  1. Print both Number values from the failure diff and fix the arithmetic that produces the wrong result
  2. Pass an appropriate $delta to assertEquals to tolerate rounding differences
  3. Normalize scales with ->round() or ->setScale() (or number_format) on both sides before comparing
  4. Update the expected literal if the new precise value is correct

Example fix

// before
$this->assertEquals(new Number('0.30'), new Number('0.1') + new Number('0.2')); // 0.3 vs 0.30 fails at delta 0
// after
$this->assertEqualsWithDelta(new Number('0.30'), new Number('0.1') + new Number('0.2'), 0.001);
Defensive patterns

Strategy: validation

Validate before calling

if (abs((float)(string)$actual - (float)(string)$expected) > $tolerance) {
    throw new InvalidArgumentException('Number values differ beyond tolerance');
}

Type guard

function isBcNumber(mixed $v): bool { return $v instanceof \BcMath\Number || is_int($v) || (is_string($v) && is_numeric($v)); }

Try / catch

try {
    $comparator->assertEquals($expected, $actual, $delta);
} catch (ComparisonFailure $e) {
    // log both string representations from $e->getExpected()/getActual()
}

Prevention

When it happens

Trigger: assertEquals() on two BcMath\Number values (possibly mixed with int/numeric-string) whose difference exceeds the given $delta, e.g. assertEquals(new Number('1.50'), new Number('2.00')) with delta 0 or too small.

Common situations: Testing money/decimal calculations where rounding or scale differences (1.5 vs 1.50 arithmetic results) make values differ; comparing bcmath results against literals without accounting for scale; passing a delta smaller than the accumulated floating/rounding error.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of sebastianbergmann/comparator@00837a9d22 (2026-09-15). Data as JSON: /api/errors/99ccd3d745a119dc. Report an issue: GitHub.

Appendix: source

Thrown at src/NumberComparator.php:61

    public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void
    {
        if (!$expected instanceof Number) {
            assert(is_int($expected) || is_string($expected) && is_numeric($expected));

            $expected = new Number($expected);
        }

        if (!$actual instanceof Number) {
            assert(is_int($actual) || is_string($actual) && is_numeric($actual));

            $actual = new Number($actual);
        }

        /** @phpstan-ignore argument.type */
        $deltaNumber = new Number(number_format($delta, max($expected->scale, $actual->scale)));

        if ($actual < $expected - $deltaNumber || $actual > $expected + $deltaNumber) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                (string) $expected,
                (string) $actual,
                'Failed asserting that two Number objects are equal.',
                $this->contextLines(),
            );
        }
    }
}

View on GitHub (pinned to 00837a9d22)