toMinorUnits() > self::fromMax()->toMinorUnits()) { throw new InvalidArgumentException('Сумма превышает допустимый максимум.'); } return $money; } /** * Сумма в копейках; безопасный путь для арифметики. */ public static function fromMinorUnits(int $minorUnits): self { if ($minorUnits < 0) { throw new InvalidArgumentException('Сумма не может быть отрицательной.'); } return self::fromDecimal(sprintf('%d.%02d', intdiv($minorUnits, 100), $minorUnits % 100)); } /** * Граница с хостом: Moodle считает стоимость `float` (`helper::get_rounded_cost`). * Округление до копеек делается здесь один раз, дальше `float` не используется. */ public static function fromFloat(float $amount): self { if ($amount < 0 || !is_finite($amount)) { throw new InvalidArgumentException('Сумма хоста должна быть конечным неотрицательным числом.'); } return self::fromDecimal(number_format($amount, 2, '.', '')); } public function toDecimal(): string { return $this->value; } public function toMinorUnits(): int { [$units, $cents] = explode('.', $this->value); return ((int) $units * 100) + (int) $cents; } public function isPositive(): bool { return $this->toMinorUnits() > 0; } public function equals(self $other): bool { return $this->value === $other->value; } public function add(self $other): self { return self::fromMinorUnits($this->toMinorUnits() + $other->toMinorUnits()); } public function multiply(int $quantity): self { if ($quantity < 0) { throw new InvalidArgumentException('Количество не может быть отрицательным.'); } return self::fromMinorUnits($this->toMinorUnits() * $quantity); } public function __toString(): string { return $this->value; } private static function fromMax(): self { return new self(self::MAX); } }