GmpInt.php

PHP

Path: src/Util/Math/Int/GmpInt.php

<?php

namespace mini\Util\Math\Int;

/**
 * GMP-based implementation of IntValue
 *
 * Wraps \GMP internally for maximum performance with large numbers.
 * Requires the gmp extension.
 */
final class GmpInt implements IntValue
{
    private function __construct(
        private readonly \GMP $value
    ) {}

    public static function of(string|int $value): static
    {
        return new self(gmp_init($value));
    }

    public static function zero(): static
    {
        return new self(gmp_init(0));
    }

    public static function one(): static
    {
        return new self(gmp_init(1));
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Arithmetic
    // ─────────────────────────────────────────────────────────────────────────

    public function add(string|int|IntValue $other): static
    {
        return new self(gmp_add($this->value, $this->toGmp($other)));
    }

    public function subtract(string|int|IntValue $other): static
    {
        return new self(gmp_sub($this->value, $this->toGmp($other)));
    }

    public function multiply(string|int|IntValue $other): static
    {
        return new self(gmp_mul($this->value, $this->toGmp($other)));
    }

    public function divide(string|int|IntValue $other): static
    {
        $divisor = $this->toGmp($other);
        if (gmp_cmp($divisor, 0) === 0) {
            throw new \DivisionByZeroError('Division by zero');
        }
        return new self(gmp_div_q($this->value, $divisor));
    }

    public function modulus(string|int|IntValue $other): static
    {
        $divisor = $this->toGmp($other);
        if (gmp_cmp($divisor, 0) === 0) {
            throw new \DivisionByZeroError('Modulus by zero');
        }
        // gmp_mod always returns non-negative, we need truncated modulus
        // a mod b = a - (a / b) * b
        $quotient = gmp_div_q($this->value, $divisor);
        return new self(gmp_sub($this->value, gmp_mul($quotient, $divisor)));
    }

    public function power(int $exponent): static
    {
        if ($exponent < 0) {
            throw new \InvalidArgumentException('Negative exponents not supported for integers');
        }
        return new self(gmp_pow($this->value, $exponent));
    }

    public function negate(): static
    {
        return new self(gmp_neg($this->value));
    }

    public function absolute(): static
    {
        return new self(gmp_abs($this->value));
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Comparison
    // ─────────────────────────────────────────────────────────────────────────

    public function compare(string|int|IntValue $other): int
    {
        return gmp_cmp($this->value, $this->toGmp($other));
    }

    public function equals(string|int|IntValue $other): bool
    {
        return $this->compare($other) === 0;
    }

    public function lessThan(string|int|IntValue $other): bool
    {
        return $this->compare($other) < 0;
    }

    public function greaterThan(string|int|IntValue $other): bool
    {
        return $this->compare($other) > 0;
    }

    public function lessThanOrEqual(string|int|IntValue $other): bool
    {
        return $this->compare($other) <= 0;
    }

    public function greaterThanOrEqual(string|int|IntValue $other): bool
    {
        return $this->compare($other) >= 0;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Predicates
    // ─────────────────────────────────────────────────────────────────────────

    public function isZero(): bool
    {
        return gmp_cmp($this->value, 0) === 0;
    }

    public function isPositive(): bool
    {
        return gmp_cmp($this->value, 0) > 0;
    }

    public function isNegative(): bool
    {
        return gmp_cmp($this->value, 0) < 0;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Conversion
    // ─────────────────────────────────────────────────────────────────────────

    public function toInt(): int
    {
        if (gmp_cmp($this->value, PHP_INT_MAX) > 0 || gmp_cmp($this->value, PHP_INT_MIN) < 0) {
            throw new \OverflowException('Value exceeds native int range');
        }
        return gmp_intval($this->value);
    }

    public function __toString(): string
    {
        return gmp_strval($this->value);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Internal
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Convert any input to GMP, preserving GMP objects from same implementation
     */
    private function toGmp(string|int|IntValue $value): \GMP
    {
        if ($value instanceof self) {
            return $value->value;
        }
        return gmp_init((string) $value);
    }
}