Skip to content

Commit 801fa5c

Browse files
committed
fix: Fixes scaling when getting float values.
1 parent b79c4ff commit 801fa5c

File tree

8 files changed

+118
-243
lines changed

8 files changed

+118
-243
lines changed

infection.json.dist

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"BCMath": false,
1818
"CastInt": false,
1919
"Ternary": false,
20+
"RoundingFamily": false,
21+
"DecrementInteger": false,
22+
"IncrementInteger": false,
2023
"ProtectedVisibility": false
2124
},
2225
"phpUnit": {

src/Internal/BigNumberBehavior.php

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use TinyBlocks\Math\Internal\Operations\Extension\ExtensionAdapter;
1010
use TinyBlocks\Math\Internal\Operations\MathOperations;
1111
use TinyBlocks\Math\Internal\Operations\MathOperationsFactory;
12-
use TinyBlocks\Math\Internal\Operations\Rounding\Rounder;
1312
use TinyBlocks\Math\RoundingMode;
1413

1514
abstract class BigNumberBehavior implements BigNumber
@@ -66,8 +65,7 @@ public function divide(BigNumber $divisor): BigNumber
6665

6766
public function withRounding(RoundingMode $mode): BigNumber
6867
{
69-
$rounder = new Rounder(mode: $mode, bigNumber: $this);
70-
$rounded = $rounder->round();
68+
$rounded = $mode->round(bigNumber: $this);
7169

7270
return static::fromString(value: $rounded->value, scale: $this->scale->value);
7371
}
@@ -131,7 +129,7 @@ public function isGreaterThanOrEqual(BigNumber $other): bool
131129

132130
public function toFloat(): float
133131
{
134-
return (float)$this->toString();
132+
return $this->number->toFloatWithScale(scale: $this->scale);
135133
}
136134

137135
public function toString(): string

src/Internal/Number.php

+7
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ public function isGreaterThanOrEqual(Number $other): bool
9696
return $this->value >= $other->value;
9797
}
9898

99+
public function toFloatWithScale(Scale $scale): float
100+
{
101+
$value = (float)$this->value;
102+
103+
return $scale->hasAutomaticScale() ? $value : (float)number_format($value, (int)$scale->value, '.', '');
104+
}
105+
99106
private function match(string $key): ?string
100107
{
101108
$math = $this->matches[$key] ?? null;

src/Internal/Operations/Rounding/Rounder.php

-27
This file was deleted.

src/Internal/Scale.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
final readonly class Scale
1111
{
12-
private const MINIMUM = 0;
12+
public const MINIMUM = 0;
1313
private const MAXIMUM = 2147483647;
1414
private const ZERO_DECIMAL_PLACE = 0;
1515

src/RoundingMode.php

+50-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace TinyBlocks\Math;
66

7+
use TinyBlocks\Math\Internal\Number;
8+
use TinyBlocks\Math\Internal\Scale;
9+
710
/**
811
* Use one of the following constants to specify the mode in which rounding occurs.
912
*
@@ -12,7 +15,52 @@
1215
enum RoundingMode: int
1316
{
1417
case HALF_UP = 1;
15-
case HALF_DOWN = 2;
16-
case HALF_EVEN = 3;
1718
case HALF_ODD = 4;
19+
case HALF_EVEN = 3;
20+
case HALF_DOWN = 2;
21+
22+
public function round(BigNumber $bigNumber): Number
23+
{
24+
$precision = $bigNumber->getScale() ?? Scale::MINIMUM;
25+
$factor = 10 ** $precision;
26+
$value = $bigNumber->toFloat();
27+
28+
$roundedValue = match ($this) {
29+
self::HALF_UP => $this->roundHalfUp(value: $value, precision: $precision),
30+
self::HALF_ODD => $this->roundHalfOdd(value: $value, factor: $factor),
31+
self::HALF_EVEN => $this->roundHalfEven(value: $value, precision: $precision),
32+
self::HALF_DOWN => $this->roundHalfDown(value: $value, factor: $factor)
33+
};
34+
35+
return Number::from($roundedValue);
36+
}
37+
38+
private function roundHalfUp(float $value, int $precision): float
39+
{
40+
return round($value, $precision);
41+
}
42+
43+
private function roundHalfOdd(float $value, int $factor): float
44+
{
45+
$scaledValue = $value * $factor;
46+
$rounded = round($scaledValue);
47+
48+
if ($rounded % 2 === 0) {
49+
$rounded += ($scaledValue > $rounded) ? 1 : -1;
50+
}
51+
52+
return $rounded / $factor;
53+
}
54+
55+
private function roundHalfEven(float $value, int $precision): float
56+
{
57+
return round($value, $precision, PHP_ROUND_HALF_EVEN);
58+
}
59+
60+
private function roundHalfDown(float $value, int $factor): float
61+
{
62+
$value = ($value * $factor - 0.5) / $factor;
63+
64+
return floor($value * $factor) / $factor;
65+
}
1866
}

tests/BigNumberTest.php

+55-71
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public function testAdd(int $scale, mixed $value, mixed $other, array $expected)
4040

4141
/** @Then the result should have the correct scale and values */
4242
self::assertSame($scale, $actual->getScale());
43+
self::assertSame($expected['float'], $actual->toFloat());
4344
self::assertSame($expected['string'], $actual->toString());
44-
self::assertSame(floatval($expected['float']), $actual->toFloat());
4545
}
4646

4747
#[DataProvider('providerForTestSubtract')]
@@ -56,8 +56,8 @@ public function testSubtract(int $scale, mixed $value, mixed $other, array $expe
5656

5757
/** @Then the result should have the correct scale and values */
5858
self::assertSame($scale, $actual->getScale());
59+
self::assertSame($expected['float'], $actual->toFloat());
5960
self::assertSame($expected['string'], $actual->toString());
60-
self::assertSame(floatval($expected['float']), $actual->toFloat());
6161
}
6262

6363
#[DataProvider('providerForTestMultiply')]
@@ -72,8 +72,8 @@ public function testMultiply(int $scale, mixed $value, mixed $other, array $expe
7272

7373
/** @Then the result should have the correct scale and values */
7474
self::assertSame($scale, $actual->getScale());
75+
self::assertSame($expected['float'], $actual->toFloat());
7576
self::assertSame($expected['string'], $actual->toString());
76-
self::assertSame(floatval($expected['float']), $actual->toFloat());
7777
}
7878

7979
#[DataProvider('providerForTestDivide')]
@@ -88,8 +88,8 @@ public function testDivide(int $scale, mixed $value, mixed $other, array $expect
8888

8989
/** @Then the result should have the correct scale and values */
9090
self::assertSame($scale, $actual->getScale());
91+
self::assertSame($expected['float'], $actual->toFloat());
9192
self::assertSame($expected['string'], $actual->toString());
92-
self::assertSame(floatval($expected['float']), $actual->toFloat());
9393
}
9494

9595
#[DataProvider('providerForTestDivisionByZero')]
@@ -120,24 +120,23 @@ public function testWithRounding(RoundingMode $mode, int $scale, mixed $value, a
120120

121121
/** @Then the result should match the expected values */
122122
self::assertSame($scale, $actual->getScale());
123+
self::assertSame($expected['float'], $actual->toFloat());
123124
self::assertSame($expected['string'], $actual->toString());
124-
self::assertSame(floatval($expected['float']), $actual->toFloat());
125125
}
126126

127127
#[DataProvider('providerForTestWithScale')]
128-
public function testWithScale(mixed $value, int $scale, int $withScale, array $expected): void
128+
public function testWithScale(mixed $value, int $scale, array $expected): void
129129
{
130-
/** @Given a BigNumber instance to be scaled */
131-
$number = LargeNumber::fromFloat(value: $value, scale: $scale);
130+
/** @Given a BigNumber instance */
131+
$number = LargeNumber::fromFloat(value: $value);
132132

133133
/** @When applying a new scale to the BigNumber */
134-
$actual = $number->withScale(scale: $withScale);
134+
$actual = $number->withScale(scale: $scale);
135135

136136
/** @Then the result should have the correct adjusted scale and values */
137-
self::assertSame($scale, $number->getScale());
138-
self::assertSame($withScale, $actual->getScale());
137+
self::assertSame($scale, $actual->getScale());
138+
self::assertSame($expected['float'], $actual->toFloat());
139139
self::assertSame($expected['string'], $actual->toString());
140-
self::assertSame(floatval($expected['float']), $actual->toFloat());
141140
}
142141

143142
#[DataProvider('providerForTestIsZero')]
@@ -256,7 +255,7 @@ public static function providerForTestAdd(): array
256255
'scale' => 0,
257256
'value' => '1',
258257
'other' => '1',
259-
'expected' => ['float' => 2, 'string' => '2']
258+
'expected' => ['float' => 2.0, 'string' => '2']
260259
],
261260
'Adding with scale' => [
262261
'scale' => 3,
@@ -268,7 +267,7 @@ public static function providerForTestAdd(): array
268267
'scale' => 0,
269268
'value' => '123',
270269
'other' => '-999',
271-
'expected' => ['float' => -876, 'string' => '-876']
270+
'expected' => ['float' => -876.0, 'string' => '-876']
272271
],
273272
'Adding large numbers with decimal' => [
274273
'scale' => 4,
@@ -298,7 +297,7 @@ public static function providerForTestSubtract(): array
298297
'scale' => 0,
299298
'value' => '11',
300299
'other' => '12',
301-
'expected' => ['float' => -1, 'string' => '-1']
300+
'expected' => ['float' => -1.0, 'string' => '-1']
302301
],
303302
'Subtraction with scale' => [
304303
'scale' => 4,
@@ -316,7 +315,7 @@ public static function providerForTestMultiply(): array
316315
'scale' => 0,
317316
'value' => '2',
318317
'other' => '2',
319-
'expected' => ['float' => 4, 'string' => '4']
318+
'expected' => ['float' => 4.0, 'string' => '4']
320319
],
321320
'Multiplying negatives' => [
322321
'scale' => 4,
@@ -358,13 +357,13 @@ public static function providerForTestDivide(): array
358357
'scale' => 0,
359358
'value' => '0.00',
360359
'other' => '8',
361-
'expected' => ['float' => 0, 'string' => '0']
360+
'expected' => ['float' => 0.0, 'string' => '0']
362361
],
363362
'Division resulting in negative' => [
364363
'scale' => 0,
365364
'value' => '-7',
366365
'other' => '0.2',
367-
'expected' => ['float' => -35, 'string' => '-35']
366+
'expected' => ['float' => -35.0, 'string' => '-35']
368367
]
369368
];
370369
}
@@ -385,7 +384,7 @@ public static function providerForTestWithRounding(): array
385384
'mode' => RoundingMode::HALF_UP,
386385
'scale' => 2,
387386
'value' => 0.9950,
388-
'expected' => ['float' => 1, 'string' => '1']
387+
'expected' => ['float' => 1.0, 'string' => '1']
389388
],
390389
'Half odd rounding' => [
391390
'mode' => RoundingMode::HALF_ODD,
@@ -403,73 +402,58 @@ public static function providerForTestWithRounding(): array
403402
'mode' => RoundingMode::HALF_EVEN,
404403
'scale' => 2,
405404
'value' => 0.9950,
406-
'expected' => ['float' => 1, 'string' => '1']
405+
'expected' => ['float' => 1.0, 'string' => '1']
407406
]
408407
];
409408
}
410409

411410
public static function providerForTestWithScale(): array
412411
{
413412
return [
414-
'Scaling zero' => [
415-
'value' => 0,
416-
'scale' => 2,
417-
'withScale' => 2,
418-
'expected' => ['float' => 0.00, 'string' => '0.00']
419-
],
420-
'Scaling zero with one decimal' => [
421-
'value' => 0.0,
422-
'scale' => 2,
423-
'withScale' => 2,
424-
'expected' => ['float' => 0.00, 'string' => '0.00']
413+
'Zero scale with no decimals' => [
414+
'value' => 0,
415+
'scale' => 0,
416+
'expected' => ['float' => 0.0, 'string' => '0']
425417
],
426-
'Scaling zero with two decimals' => [
427-
'value' => 0.00,
428-
'scale' => 3,
429-
'withScale' => 3,
430-
'expected' => ['float' => 0.000, 'string' => '0.000']
418+
'Zero scale with one decimal place' => [
419+
'value' => 0,
420+
'scale' => 1,
421+
'expected' => ['float' => 0.0, 'string' => '0.0']
431422
],
432-
'Scaling zero with three decimals' => [
433-
'value' => 0.000,
434-
'scale' => 1,
435-
'withScale' => 1,
436-
'expected' => ['float' => 0.0, 'string' => '0.0']
423+
'Zero scale with two decimal places' => [
424+
'value' => 0,
425+
'scale' => 2,
426+
'expected' => ['float' => 0.00, 'string' => '0.00']
437427
],
438-
'Scaling with integer' => [
439-
'value' => 5,
440-
'scale' => 2,
441-
'withScale' => 2,
442-
'expected' => ['float' => 5.00, 'string' => '5.00']
428+
'Zero scale with three decimal places' => [
429+
'value' => 0,
430+
'scale' => 3,
431+
'expected' => ['float' => 0.000, 'string' => '0.000']
443432
],
444-
'Scaling with decimal and reducing precision' => [
445-
'value' => 123.4567,
446-
'scale' => 2,
447-
'withScale' => 2,
448-
'expected' => ['float' => 123.45, 'string' => '123.45']
433+
'Negative large value rounded to one decimal' => [
434+
'value' => -553.99999,
435+
'scale' => 1,
436+
'expected' => ['float' => -553.9, 'string' => '-553.9']
449437
],
450-
'Scaling large negative number' => [
451-
'value' => -553.99999,
452-
'scale' => 5,
453-
'withScale' => 1,
454-
'expected' => ['float' => -553.9, 'string' => '-553.9']
438+
'Large positive number rounded to two decimals' => [
439+
'value' => 999999.999,
440+
'scale' => 2,
441+
'expected' => ['float' => 999999.99, 'string' => '999999.99']
455442
],
456-
'Scaling with precision reduction' => [
457-
'value' => 10.5555,
458-
'scale' => 4,
459-
'withScale' => 3,
460-
'expected' => ['float' => 10.555, 'string' => '10.555']
443+
'Small negative value rounded to four decimals' => [
444+
'value' => -0.12345,
445+
'scale' => 4,
446+
'expected' => ['float' => -0.1234, 'string' => '-0.1234']
461447
],
462-
'Scaling large positive number' => [
463-
'value' => 999999.999,
464-
'scale' => 2,
465-
'withScale' => 2,
466-
'expected' => ['float' => 999999.99, 'string' => '999999.99']
448+
'Decimal value with precision reduction to two' => [
449+
'value' => 123.4567,
450+
'scale' => 2,
451+
'expected' => ['float' => 123.45, 'string' => '123.45']
467452
],
468-
'Scaling with small negative number' => [
469-
'value' => -0.12345,
470-
'scale' => 4,
471-
'withScale' => 4,
472-
'expected' => ['float' => -0.1234, 'string' => '-0.1234']
453+
'Positive value with precision reduction to three' => [
454+
'value' => 10.5555,
455+
'scale' => 3,
456+
'expected' => ['float' => 10.555, 'string' => '10.555']
473457
]
474458
];
475459
}

0 commit comments

Comments
 (0)