BigInt.php

PHP

Path: src/Util/Math/BigInt.php

<?php

namespace mini\Util\Math;

use mini\Util\Math\Int\BcMathInt;
use mini\Util\Math\Int\GmpInt;
use mini\Util\Math\Int\IntValue;
use mini\Util\Math\Int\NativeInt;

/**
 * Immutable arbitrary precision integer
 *
 * This is the public API for arbitrary precision integer math.
 * Internally uses the best available implementation (GMP, bcmath, or pure PHP).
 *
 * Serialization stores only the string value, so serialized data is portable
 * across different PHP installations regardless of available extensions.
 *
 * Usage:
 *   $a = BigInt::of('123456789012345678901234567890');
 *   $b = BigInt::of(42);
 *   $result = $a->add($b)->multiply($a);
 *   echo $result; // prints the number
 */
final class BigInt implements NumberInterface
{
    private static ?string $implementation = null;

    private function __construct(
        private readonly IntValue $value
    ) {}

    // ─────────────────────────────────────────────────────────────────────────
    // Factory methods
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Create from string or int
     */
    public static function of(string|int $value): self
    {
        return new self(self::createValue($value));
    }

    /**
     * Create zero
     */
    public static function zero(): self
    {
        return self::of(0);
    }

    /**
     * Create one
     */
    public static function one(): self
    {
        return self::of(1);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Arithmetic operations (return new instance)
    // ─────────────────────────────────────────────────────────────────────────

    public function add(self|string|int $other): self
    {
        return new self($this->value->add($this->unwrap($other)));
    }

    public function subtract(self|string|int $other): self
    {
        return new self($this->value->subtract($this->unwrap($other)));
    }

    public function multiply(self|string|int $other): self
    {
        return new self($this->value->multiply($this->unwrap($other)));
    }

    /**
     * Integer division (truncates toward zero)
     *
     * @throws \DivisionByZeroError
     */
    public function divide(self|string|int $other): self
    {
        return new self($this->value->divide($this->unwrap($other)));
    }

    /**
     * Remainder after integer division
     *
     * @throws \DivisionByZeroError
     */
    public function modulus(self|string|int $other): self
    {
        return new self($this->value->modulus($this->unwrap($other)));
    }

    /**
     * Raise to integer power
     *
     * @throws \InvalidArgumentException if exponent is negative
     */
    public function power(int $exponent): self
    {
        return new self($this->value->power($exponent));
    }

    /**
     * Negate: -x
     */
    public function negate(): self
    {
        return new self($this->value->negate());
    }

    /**
     * Absolute value
     */
    public function absolute(): self
    {
        return new self($this->value->absolute());
    }

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

    /**
     * Compare: returns -1 if less, 0 if equal, 1 if greater
     */
    public function compare(self|string|int $other): int
    {
        return $this->value->compare($this->unwrap($other));
    }

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

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

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

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

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

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

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

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

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

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

    /**
     * Scale is always 0 for integers
     */
    public function scale(): int
    {
        return 0;
    }

    /**
     * Convert to native int
     *
     * @throws \OverflowException if value exceeds PHP_INT_MAX/PHP_INT_MIN
     */
    public function toInt(): int
    {
        return $this->value->toInt();
    }

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

    // ─────────────────────────────────────────────────────────────────────────
    // Serialization (implementation-agnostic)
    // ─────────────────────────────────────────────────────────────────────────

    public function __serialize(): array
    {
        return ['v' => (string) $this->value];
    }

    public function __unserialize(array $data): void
    {
        // Use reflection to set readonly property during unserialization
        $ref = new \ReflectionProperty(self::class, 'value');
        $ref->setValue($this, self::createValue($data['v']));
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Implementation selection
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Get the name of the current implementation
     */
    public static function getImplementation(): string
    {
        return self::$implementation ??= self::detectImplementation();
    }

    /**
     * Force a specific implementation (mainly for testing)
     *
     * @param 'gmp'|'bcmath'|'native'|null $impl
     */
    public static function setImplementation(?string $impl): void
    {
        self::$implementation = $impl;
    }

    private static function detectImplementation(): string
    {
        if (extension_loaded('gmp')) {
            return 'gmp';
        }
        if (extension_loaded('bcmath')) {
            return 'bcmath';
        }
        return 'native';
    }

    private static function createValue(string|int $value): IntValue
    {
        return match (self::getImplementation()) {
            'gmp' => GmpInt::of($value),
            'bcmath' => BcMathInt::of($value),
            default => NativeInt::of($value),
        };
    }

    /**
     * Unwrap BigInt to string for internal IntValue operations
     */
    private function unwrap(self|string|int $value): string
    {
        return $value instanceof self ? (string) $value->value : (string) $value;
    }
}