91 lines
2.6 KiB
PHP
91 lines
2.6 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Ledger;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
final class AssetAmount {
|
|
private function __construct(
|
|
private readonly AssetType $asset,
|
|
private readonly int $minor
|
|
) {}
|
|
|
|
public static function points(int $points): self {
|
|
if ($points < 0) {
|
|
throw new \InvalidArgumentException('points_must_be_non_negative');
|
|
}
|
|
return new self(AssetType::POINTS, $points);
|
|
}
|
|
|
|
public static function balance(string $decimal): self {
|
|
$decimal = trim($decimal);
|
|
if (!preg_match('/^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/', $decimal, $matches)) {
|
|
throw new \InvalidArgumentException('balance_decimal_invalid');
|
|
}
|
|
$whole = (int) $matches[1];
|
|
$fraction = str_pad((string) ($matches[2] ?? ''), 2, '0');
|
|
if ($whole > intdiv(PHP_INT_MAX - (int) $fraction, 100)) {
|
|
throw new \InvalidArgumentException('balance_decimal_overflow');
|
|
}
|
|
return new self(AssetType::BALANCE, ($whole * 100) + (int) $fraction);
|
|
}
|
|
|
|
public static function balance_minor(string $decimal): int {
|
|
return self::balance($decimal)->minor();
|
|
}
|
|
|
|
public static function decimal_from_minor(int $minor): string {
|
|
return self::from_minor(AssetType::BALANCE, $minor)->decimal();
|
|
}
|
|
|
|
/**
|
|
* Apply an integer percentage using half-up rounding without floats.
|
|
*/
|
|
public static function percentage(int $minor, int $percent): int {
|
|
if ($minor < 0) {
|
|
throw new \InvalidArgumentException('asset_minor_must_be_non_negative');
|
|
}
|
|
if ($percent < 0 || $percent > 100) {
|
|
throw new \InvalidArgumentException('asset_percentage_invalid');
|
|
}
|
|
|
|
$whole = intdiv($minor, 100) * $percent;
|
|
$remainder = ($minor % 100) * $percent;
|
|
return $whole + intdiv($remainder + 50, 100);
|
|
}
|
|
|
|
public static function from_minor(AssetType|string $asset, int $minor): self {
|
|
if ($minor < 0) {
|
|
throw new \InvalidArgumentException('asset_minor_must_be_non_negative');
|
|
}
|
|
return new self(AssetType::from_value($asset), $minor);
|
|
}
|
|
|
|
public function asset(): AssetType {
|
|
return $this->asset;
|
|
}
|
|
|
|
public function minor(): int {
|
|
return $this->minor;
|
|
}
|
|
|
|
public function decimal(): string {
|
|
if ($this->asset === AssetType::POINTS) {
|
|
return (string) $this->minor;
|
|
}
|
|
return sprintf('%d.%02d', intdiv($this->minor, 100), $this->minor % 100);
|
|
}
|
|
|
|
public function formatted(): string {
|
|
if ($this->asset === AssetType::POINTS) {
|
|
return number_format_i18n($this->minor);
|
|
}
|
|
global $wp_locale;
|
|
$decimal_point = isset($wp_locale->number_format['decimal_point']) ? $wp_locale->number_format['decimal_point'] : '.';
|
|
return number_format_i18n(intdiv($this->minor, 100)) . $decimal_point . sprintf('%02d', $this->minor % 100);
|
|
}
|
|
}
|