Skip to content

Commit a9421cd

Browse files
authored
Fix up rounding for small values. (#18)
* Fix up rounding for small values. * Fix up rounding for small values.
1 parent 539fd1d commit a9421cd

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"require-dev": {
2222
"php-collective/code-sniffer": "^0.2.1",
2323
"phpstan/phpstan": "^2.0.0",
24-
"phpunit/phpunit": "^11.0.0"
24+
"phpunit/phpunit": "^10.5.45 || ^11.0.0"
2525
},
2626
"minimum-stability": "stable",
2727
"prefer-stable": true,

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ parameters:
55
ignoreErrors:
66
- identifier: missingType.iterableValue
77
- '#Unsafe usage of new static\(\).#'
8-
- '#expects numeric-string, string given.#'
8+
- '#expects numeric-string, .*string given.#'

src/Decimal.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -485,22 +485,44 @@ public function round(int $scale = 0, int $roundMode = self::ROUND_HALF_UP): sta
485485
{
486486
$exponent = $scale + 1;
487487

488-
$e = bcpow('10', (string)$exponent);
489488
switch ($roundMode) {
490489
case static::ROUND_FLOOR:
491-
$v = bcdiv(bcadd(bcmul((string)$this, $e, 0), $this->isNegative() ? '-9' : '0'), $e, 0);
490+
$stringValue = (string)$this;
492491

493-
break;
492+
// If already an integer, return as is
493+
if (!str_contains($stringValue, '.')) {
494+
return new static($stringValue, $scale);
495+
}
496+
497+
// For positive values, truncate the decimal part (round down)
498+
if (!$this->isNegative()) {
499+
return new static(bcsub($stringValue, bcmod($stringValue, '1'), 0), $scale);
500+
}
501+
502+
// For negative values, round down away from zero
503+
return new static(bcsub($stringValue, '1', 0), $scale);
494504
case static::ROUND_CEIL:
495-
$v = bcdiv(bcadd(bcmul((string)$this, $e, 0), $this->isNegative() ? '0' : '9'), $e, 0);
505+
$stringValue = (string)$this;
506+
507+
// If already an integer, return as is
508+
if (!str_contains($stringValue, '.')) {
509+
return new static($stringValue, $scale);
510+
}
511+
512+
// If negative, truncate (remove decimals without adding 1)
513+
if ($this->isNegative()) {
514+
return new static(bcsub($stringValue, '0', 0), $scale);
515+
}
496516

497-
break;
517+
// Otherwise, round up for positive numbers
518+
return new static(bcadd($stringValue, '1', 0), $scale);
498519
case static::ROUND_HALF_UP:
499520
default:
521+
$e = bcpow('10', (string)$exponent);
500522
$v = bcdiv(bcadd(bcmul((string)$this, $e, 0), $this->isNegative() ? '-5' : '5'), $e, $scale);
501523
}
502524

503-
return new static($v);
525+
return new static($v, $scale);
504526
}
505527

506528
/**

tests/DecimalTest.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,12 @@ public function testRound(mixed $value, int $scale, string $expected): void
636636
*/
637637
protected function assertNativeRound(string $expected, mixed $value, int $scale, int $roundMode): void
638638
{
639-
$this->assertSame((new Decimal($expected))->trim()->toString(), (string)round((float)$value, $scale, $roundMode));
639+
$value = (string)round((float)$value, $scale, $roundMode);
640+
if ($value === '-0') {
641+
$value = '0';
642+
}
643+
644+
$this->assertSame((new Decimal($expected))->trim()->toString(), $value);
640645
}
641646

642647
/**
@@ -659,6 +664,10 @@ public static function roundProvider(): array
659664
[13.4999, 0, '13'],
660665
[13.4999, 10, '13.4999000000'],
661666
[13.4999, 2, '13.50'],
667+
[0.0001, 0, '0'],
668+
[-0.0001, 0, '0'],
669+
[0.9999, 0, '1'],
670+
[-0.9999, 0, '-1'],
662671
];
663672
}
664673

@@ -685,7 +694,12 @@ public function testFloor(mixed $value, string $expected): void
685694
*/
686695
protected function assertNativeFloor(string $expected, mixed $value): void
687696
{
688-
$this->assertSame($expected, (string)floor((float)$value));
697+
$value = (string)floor((float)$value);
698+
if ($value === '-0') {
699+
$value = '0';
700+
}
701+
702+
$this->assertSame($expected, $value);
689703
}
690704

691705
/**
@@ -706,6 +720,8 @@ public static function floorProvider(): array
706720
['13.6999', '13'],
707721
['13.1', '13'],
708722
['13.9', '13'],
723+
[0.0001, '0'],
724+
[-0.0001, '-1'],
709725
];
710726
}
711727

@@ -732,7 +748,11 @@ public function testCeil(mixed $value, string $expected): void
732748
*/
733749
protected function assertNativeCeil(string $expected, mixed $value): void
734750
{
735-
$this->assertSame($expected, (string)ceil((float)$value));
751+
$value = (string)ceil((float)$value);
752+
if ($value === '-0') {
753+
$value = '0';
754+
}
755+
$this->assertSame($expected, $value);
736756
}
737757

738758
/**
@@ -753,6 +773,8 @@ public static function ceilProvider(): array
753773
['13.6999', '14'],
754774
['13.1', '14'],
755775
['13.9', '14'],
776+
[0.0001, '1'],
777+
[-0.0001, '0'],
756778
];
757779
}
758780

0 commit comments

Comments
 (0)