$scale) { throw new InvalidArgumentException("Value has more than {$scale} decimal places: {$decimal}"); } $fraction = str_pad($fraction, $scale, '0'); $whole = $whole === '' ? '0' : $whole; $combined = ltrim($whole.$fraction, '0'); $combined = $combined === '' ? '0' : $combined; if (strlen($combined) > 18) { throw new InvalidArgumentException("Value out of bounds: {$decimal}"); } $value = (int) $combined; if ($value < 0 || $combined !== (string) $value) { throw new InvalidArgumentException("Value out of bounds: {$decimal}"); } if ($maxAbs !== null && $value > $maxAbs * (10 ** $scale)) { throw new InvalidArgumentException("Value exceeds the allowed maximum: {$decimal}"); } return $negative ? -$value : $value; } public static function add(int $a, int $b): int { if (($b > 0 && $a > self::MAX_INT - $b) || ($b < 0 && $a < self::MIN_INT - $b)) { throw new OverflowException('Fixed-point addition overflowed.'); } return $a + $b; } public static function subtract(int $a, int $b): int { return self::add($a, -$b); } public static function multiply(int $a, int $b): int { if ($a === 0 || $b === 0) { return 0; } $result = $a * $b; if (! is_int($result) || intdiv($result, $b) !== $a) { throw new OverflowException('Fixed-point multiplication overflowed.'); } return $result; } /** * Divide a scaled value by a plain positive integer (e.g. batch units), rounding half away from zero. */ public static function divideByInt(int $value, int $divisor): int { if ($divisor === 0) { throw new InvalidArgumentException('Cannot divide by zero.'); } return self::roundDivide($value, $divisor); } /** * Rescale a value from one decimal scale to another, rounding half away from zero. */ public static function rescale(int $value, int $fromScale, int $toScale): int { if ($toScale === $fromScale) { return $value; } if ($toScale > $fromScale) { $factor = 10 ** ($toScale - $fromScale); $result = $value * $factor; if ($factor !== 0 && intdiv($result, $factor) !== $value) { throw new OverflowException('Fixed-point rescale overflowed.'); } return $result; } return self::roundDivide($value, 10 ** ($fromScale - $toScale)); } public static function format(int $scaled, int $scale): string { $negative = $scaled < 0; $abs = (string) abs($scaled); $abs = str_pad($abs, $scale + 1, '0', STR_PAD_LEFT); $whole = substr($abs, 0, strlen($abs) - $scale); $fraction = $scale > 0 ? substr($abs, strlen($abs) - $scale) : ''; $formatted = $scale > 0 ? "{$whole}.{$fraction}" : $whole; return ($negative && $scaled !== 0 ? '-' : '').$formatted; } private static function roundDivide(int $value, int $divisor): int { $sign = ($value < 0) !== ($divisor < 0) ? -1 : 1; $numerator = abs($value); $denominator = abs($divisor); $quotient = intdiv($numerator, $denominator); $remainder = $numerator % $denominator; if ($remainder * 2 >= $denominator) { $quotient++; } return $sign * $quotient; } }