bcmath and currency float casting fix

This commit is contained in:
Yurii
2026-02-16 13:52:39 +02:00
parent 299709f3ee
commit d74acd7776
3 changed files with 74 additions and 56 deletions

View File

@@ -33,7 +33,7 @@ use DivisionByZeroError;
class CalculatorUtil
{
private const SCALE = 14;
private const int SCALE = 14;
/**
* @param numeric-string $arg1
@@ -43,16 +43,10 @@ class CalculatorUtil
public static function add(string $arg1, string $arg2): string
{
if (!function_exists('bcadd')) {
return (string) (
(float) $arg1 + (float) $arg2
);
return self::floatToString((float) $arg1 + (float) $arg2);
}
return bcadd(
$arg1,
$arg2,
self::SCALE
);
return bcadd($arg1, $arg2, self::SCALE);
}
/**
@@ -63,56 +57,42 @@ class CalculatorUtil
public static function subtract(string $arg1, string $arg2): string
{
if (!function_exists('bcsub')) {
return (string) (
(float) $arg1 - (float) $arg2
);
return self::floatToString((float) $arg1 - (float) $arg2);
}
return bcsub(
$arg1,
$arg2,
self::SCALE
);
return bcsub($arg1, $arg2, self::SCALE);
}
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
* @return numeric-string
*
* @todo For the result, trim right zeros. Then, trim dot.
*/
public static function multiply(string $arg1, string $arg2): string
{
if (!function_exists('bcmul')) {
return (string) (
(float) $arg1 * (float) $arg2
);
return self::floatToString((float) $arg1 * (float) $arg2);
}
return bcmul(
$arg1,
$arg2,
self::SCALE
);
return bcmul($arg1, $arg2, self::SCALE);
}
/**
* @param numeric-string $arg1
* @param numeric-string $arg2
* @return numeric-string
*
* @todo For the result, trim right zeros. Then, trim dot.
*/
public static function divide(string $arg1, string $arg2): string
{
if (!function_exists('bcdiv')) {
return (string) (
(float) $arg1 / (float) $arg2
);
return self::floatToString((float) $arg1 / (float) $arg2);
}
$result = bcdiv(
$arg1,
$arg2,
self::SCALE
);
$result = bcdiv($arg1, $arg2, self::SCALE);
if ($result === null) { /** @phpstan-ignore-line */
throw new DivisionByZeroError();
@@ -128,7 +108,9 @@ class CalculatorUtil
public static function round(string $arg, int $precision = 0): string
{
if (!function_exists('bcadd')) {
return (string) round((float) $arg, $precision);
return self::floatToString(
round((float) $arg, $precision)
);
}
$addition = '0.' . str_repeat('0', $precision) . '5';
@@ -139,11 +121,7 @@ class CalculatorUtil
assert(is_numeric($addition));
return bcadd(
$arg,
$addition,
$precision
);
return bcadd($arg, $addition, $precision);
}
/**
@@ -156,10 +134,15 @@ class CalculatorUtil
return (float) $arg1 <=> (float) $arg2;
}
return bccomp(
$arg1,
$arg2,
self::SCALE
);
return bccomp($arg1, $arg2, self::SCALE);
}
/**
* @return numeric-string
*/
private static function floatToString(float $amount): string
{
/** @var numeric-string */
return rtrim(rtrim(sprintf('%.' . self::SCALE . 'f', $amount), '0'), '.');
}
}

View File

@@ -38,6 +38,8 @@ use InvalidArgumentException;
*/
class Currency
{
private const int DEFAULT_SCALE = 14;
/** @var numeric-string */
private string $amount;
private string $code;
@@ -57,8 +59,10 @@ class Currency
throw new InvalidArgumentException("Bad currency code.");
}
if (is_float($amount) || is_int($amount)) {
if (is_int($amount)) {
$amount = (string) $amount;
} else if (is_float($amount)) {
$amount = self::floatToString($amount);
}
$this->amount = $amount;
@@ -136,10 +140,9 @@ class Currency
*/
public function multiply(float|int|string $multiplier): self
{
$amount = CalculatorUtil::multiply(
$this->getAmountAsString(),
(string) $multiplier
);
$multiplier = is_float($multiplier) ? self::floatToString($multiplier) : (string) $multiplier;
$amount = CalculatorUtil::multiply($this->getAmountAsString(), $multiplier);
return new self($amount, $this->getCode());
}
@@ -151,10 +154,9 @@ class Currency
*/
public function divide(float|int|string $divider): self
{
$amount = CalculatorUtil::divide(
$this->getAmountAsString(),
(string) $divider
);
$divider = is_float($divider) ? self::floatToString($divider) : (string) $divider;
$amount = CalculatorUtil::divide($this->getAmountAsString(), $divider);
return new self($amount, $this->getCode());
}
@@ -208,4 +210,13 @@ class Currency
{
return new self($amount, $code);
}
/**
* @return numeric-string
*/
private static function floatToString(float $amount): string
{
/** @var numeric-string */
return rtrim(rtrim(sprintf('%.' . self::DEFAULT_SCALE . 'f', $amount), '0'), '.');
}
}

View File

@@ -39,7 +39,7 @@ use InvalidArgumentException;
class CurrencyTest extends TestCase
{
public function testValue()
public function testValue1()
{
$value = Currency::create(2.0, 'USD');
@@ -47,6 +47,14 @@ class CurrencyTest extends TestCase
$this->assertEquals('USD', $value->getCode());
}
public function testValue()
{
$value = Currency::create(0.5, 'USD');
$this->assertEquals('0.5', $value->getAmountAsString());
$this->assertEquals('USD', $value->getCode());
}
public function testAdd()
{
$value = (new Currency(2.0, 'USD'))->add(
@@ -67,7 +75,7 @@ class CurrencyTest extends TestCase
$this->assertEquals('USD', $value->getCode());
}
public function testMultiply()
public function testMultiply1()
{
$value = (new Currency(2.0, 'USD'))->multiply(3.0);
@@ -75,7 +83,15 @@ class CurrencyTest extends TestCase
$this->assertEquals('USD', $value->getCode());
}
public function testDivide()
public function testMultiply2()
{
$value = (new Currency(2.0, 'USD'))->multiply(0.5);
$this->assertEquals('1.00000000000000', $value->getAmountAsString());
$this->assertEquals('USD', $value->getCode());
}
public function testDivide1()
{
$value = (new Currency(6.0, 'USD'))->divide(3.0);
@@ -83,6 +99,14 @@ class CurrencyTest extends TestCase
$this->assertEquals('USD', $value->getCode());
}
public function testDivide2()
{
$value = (new Currency(6.0, 'USD'))->divide(0.5);
$this->assertEquals('12.00000000000000', $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testRound1()
{
$value = (new Currency(2.306, 'USD'))->round(2);