BcMathInt.php

PHP

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

<?php

namespace mini\Util\Math\Int;

/**
 * bcmath-based implementation of IntValue
 *
 * Stores normalized string internally.
 * Requires the bcmath extension.
 */
final class BcMathInt implements IntValue
{
    private function __construct(
        private readonly string $value
    ) {}

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

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

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

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

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

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

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

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

    public function modulus(string|int|IntValue $other): static
    {
        $divisor = $this->toStr($other);
        if (self::normalize($divisor) === '0') {
            throw new \DivisionByZeroError('Modulus by zero');
        }
        return new self(bcmod($this->value, $divisor, 0));
    }

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

    public function negate(): static
    {
        if ($this->value === '0') {
            return $this;
        }
        return new self(
            str_starts_with($this->value, '-')
                ? substr($this->value, 1)
                : '-' . $this->value
        );
    }

    public function absolute(): static
    {
        if ($this->isNegative()) {
            return $this->negate();
        }
        return $this;
    }

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

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

    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 $this->value === '0';
    }

    public function isPositive(): bool
    {
        return $this->value !== '0' && !str_starts_with($this->value, '-');
    }

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

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

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

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

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

    private function toStr(string|int|IntValue $value): string
    {
        if ($value instanceof self) {
            return $value->value;
        }
        return (string) $value;
    }

    /**
     * Normalize: strip +, leading zeros, handle -0
     */
    private static function normalize(string $value): string
    {
        $neg = false;
        $i = 0;
        $len = strlen($value);

        if ($i < $len && ($value[$i] === '-' || $value[$i] === '+')) {
            $neg = $value[$i] === '-';
            $i++;
        }

        while ($i < $len - 1 && $value[$i] === '0') {
            $i++;
        }

        $abs = $i === 0 ? $value : substr($value, $i);
        if ($abs === '' || $abs === false) {
            return '0';
        }

        if ($abs === '0') {
            return '0';
        }

        return $neg ? '-' . $abs : $abs;
    }
}