Skip to content

Commit

Permalink
fix: Fixes scaling when getting float values.
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavofreze authored Nov 7, 2024
1 parent b79c4ff commit a06deec
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 243 deletions.
3 changes: 3 additions & 0 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"BCMath": false,
"CastInt": false,
"Ternary": false,
"RoundingFamily": false,
"DecrementInteger": false,
"IncrementInteger": false,
"ProtectedVisibility": false
},
"phpUnit": {
Expand Down
6 changes: 2 additions & 4 deletions src/Internal/BigNumberBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use TinyBlocks\Math\Internal\Operations\Extension\ExtensionAdapter;
use TinyBlocks\Math\Internal\Operations\MathOperations;
use TinyBlocks\Math\Internal\Operations\MathOperationsFactory;
use TinyBlocks\Math\Internal\Operations\Rounding\Rounder;
use TinyBlocks\Math\RoundingMode;

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

public function withRounding(RoundingMode $mode): BigNumber
{
$rounder = new Rounder(mode: $mode, bigNumber: $this);
$rounded = $rounder->round();
$rounded = $mode->round(bigNumber: $this);

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

public function toFloat(): float
{
return (float)$this->toString();
return $this->number->toFloatWithScale(scale: $this->scale);
}

public function toString(): string
Expand Down
7 changes: 7 additions & 0 deletions src/Internal/Number.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public function isGreaterThanOrEqual(Number $other): bool
return $this->value >= $other->value;
}

public function toFloatWithScale(Scale $scale): float
{
$value = (float)$this->value;

return $scale->hasAutomaticScale() ? $value : (float)number_format($value, (int)$scale->value, '.', '');
}

private function match(string $key): ?string
{
$math = $this->matches[$key] ?? null;
Expand Down
27 changes: 0 additions & 27 deletions src/Internal/Operations/Rounding/Rounder.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Internal/Scale.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

final readonly class Scale
{
private const MINIMUM = 0;
public const MINIMUM = 0;
private const MAXIMUM = 2147483647;
private const ZERO_DECIMAL_PLACE = 0;

Expand Down
52 changes: 50 additions & 2 deletions src/RoundingMode.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace TinyBlocks\Math;

use TinyBlocks\Math\Internal\Number;
use TinyBlocks\Math\Internal\Scale;

/**
* Use one of the following constants to specify the mode in which rounding occurs.
*
Expand All @@ -12,7 +15,52 @@
enum RoundingMode: int
{
case HALF_UP = 1;
case HALF_DOWN = 2;
case HALF_EVEN = 3;
case HALF_ODD = 4;
case HALF_EVEN = 3;
case HALF_DOWN = 2;

public function round(BigNumber $bigNumber): Number
{
$precision = $bigNumber->getScale() ?? Scale::MINIMUM;
$factor = 10 ** $precision;
$value = $bigNumber->toFloat();

$roundedValue = match ($this) {
self::HALF_UP => $this->roundHalfUp(value: $value, precision: $precision),
self::HALF_ODD => $this->roundHalfOdd(value: $value, factor: $factor),
self::HALF_EVEN => $this->roundHalfEven(value: $value, precision: $precision),
self::HALF_DOWN => $this->roundHalfDown(value: $value, factor: $factor)
};

return Number::from($roundedValue);
}

private function roundHalfUp(float $value, int $precision): float
{
return round($value, $precision);
}

private function roundHalfOdd(float $value, int $factor): float
{
$scaledValue = $value * $factor;
$rounded = round($scaledValue);

if ($rounded % 2 === 0) {
$rounded += ($scaledValue > $rounded) ? 1 : -1;
}

return $rounded / $factor;
}

private function roundHalfEven(float $value, int $precision): float
{
return round($value, $precision, PHP_ROUND_HALF_EVEN);
}

private function roundHalfDown(float $value, int $factor): float
{
$value = ($value * $factor - 0.5) / $factor;

return floor($value * $factor) / $factor;
}
}
126 changes: 55 additions & 71 deletions tests/BigNumberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public function testAdd(int $scale, mixed $value, mixed $other, array $expected)

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

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

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

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

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

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

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

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

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

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

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

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

#[DataProvider('providerForTestIsZero')]
Expand Down Expand Up @@ -256,7 +255,7 @@ public static function providerForTestAdd(): array
'scale' => 0,
'value' => '1',
'other' => '1',
'expected' => ['float' => 2, 'string' => '2']
'expected' => ['float' => 2.0, 'string' => '2']
],
'Adding with scale' => [
'scale' => 3,
Expand All @@ -268,7 +267,7 @@ public static function providerForTestAdd(): array
'scale' => 0,
'value' => '123',
'other' => '-999',
'expected' => ['float' => -876, 'string' => '-876']
'expected' => ['float' => -876.0, 'string' => '-876']
],
'Adding large numbers with decimal' => [
'scale' => 4,
Expand Down Expand Up @@ -298,7 +297,7 @@ public static function providerForTestSubtract(): array
'scale' => 0,
'value' => '11',
'other' => '12',
'expected' => ['float' => -1, 'string' => '-1']
'expected' => ['float' => -1.0, 'string' => '-1']
],
'Subtraction with scale' => [
'scale' => 4,
Expand All @@ -316,7 +315,7 @@ public static function providerForTestMultiply(): array
'scale' => 0,
'value' => '2',
'other' => '2',
'expected' => ['float' => 4, 'string' => '4']
'expected' => ['float' => 4.0, 'string' => '4']
],
'Multiplying negatives' => [
'scale' => 4,
Expand Down Expand Up @@ -358,13 +357,13 @@ public static function providerForTestDivide(): array
'scale' => 0,
'value' => '0.00',
'other' => '8',
'expected' => ['float' => 0, 'string' => '0']
'expected' => ['float' => 0.0, 'string' => '0']
],
'Division resulting in negative' => [
'scale' => 0,
'value' => '-7',
'other' => '0.2',
'expected' => ['float' => -35, 'string' => '-35']
'expected' => ['float' => -35.0, 'string' => '-35']
]
];
}
Expand All @@ -385,7 +384,7 @@ public static function providerForTestWithRounding(): array
'mode' => RoundingMode::HALF_UP,
'scale' => 2,
'value' => 0.9950,
'expected' => ['float' => 1, 'string' => '1']
'expected' => ['float' => 1.0, 'string' => '1']
],
'Half odd rounding' => [
'mode' => RoundingMode::HALF_ODD,
Expand All @@ -403,73 +402,58 @@ public static function providerForTestWithRounding(): array
'mode' => RoundingMode::HALF_EVEN,
'scale' => 2,
'value' => 0.9950,
'expected' => ['float' => 1, 'string' => '1']
'expected' => ['float' => 1.0, 'string' => '1']
]
];
}

public static function providerForTestWithScale(): array
{
return [
'Scaling zero' => [
'value' => 0,
'scale' => 2,
'withScale' => 2,
'expected' => ['float' => 0.00, 'string' => '0.00']
],
'Scaling zero with one decimal' => [
'value' => 0.0,
'scale' => 2,
'withScale' => 2,
'expected' => ['float' => 0.00, 'string' => '0.00']
'Zero scale with no decimals' => [
'value' => 0,
'scale' => 0,
'expected' => ['float' => 0.0, 'string' => '0']
],
'Scaling zero with two decimals' => [
'value' => 0.00,
'scale' => 3,
'withScale' => 3,
'expected' => ['float' => 0.000, 'string' => '0.000']
'Zero scale with one decimal place' => [
'value' => 0,
'scale' => 1,
'expected' => ['float' => 0.0, 'string' => '0.0']
],
'Scaling zero with three decimals' => [
'value' => 0.000,
'scale' => 1,
'withScale' => 1,
'expected' => ['float' => 0.0, 'string' => '0.0']
'Zero scale with two decimal places' => [
'value' => 0,
'scale' => 2,
'expected' => ['float' => 0.00, 'string' => '0.00']
],
'Scaling with integer' => [
'value' => 5,
'scale' => 2,
'withScale' => 2,
'expected' => ['float' => 5.00, 'string' => '5.00']
'Zero scale with three decimal places' => [
'value' => 0,
'scale' => 3,
'expected' => ['float' => 0.000, 'string' => '0.000']
],
'Scaling with decimal and reducing precision' => [
'value' => 123.4567,
'scale' => 2,
'withScale' => 2,
'expected' => ['float' => 123.45, 'string' => '123.45']
'Negative large value rounded to one decimal' => [
'value' => -553.99999,
'scale' => 1,
'expected' => ['float' => -553.9, 'string' => '-553.9']
],
'Scaling large negative number' => [
'value' => -553.99999,
'scale' => 5,
'withScale' => 1,
'expected' => ['float' => -553.9, 'string' => '-553.9']
'Large positive number rounded to two decimals' => [
'value' => 999999.999,
'scale' => 2,
'expected' => ['float' => 999999.99, 'string' => '999999.99']
],
'Scaling with precision reduction' => [
'value' => 10.5555,
'scale' => 4,
'withScale' => 3,
'expected' => ['float' => 10.555, 'string' => '10.555']
'Small negative value rounded to four decimals' => [
'value' => -0.12345,
'scale' => 4,
'expected' => ['float' => -0.1234, 'string' => '-0.1234']
],
'Scaling large positive number' => [
'value' => 999999.999,
'scale' => 2,
'withScale' => 2,
'expected' => ['float' => 999999.99, 'string' => '999999.99']
'Decimal value with precision reduction to two' => [
'value' => 123.4567,
'scale' => 2,
'expected' => ['float' => 123.45, 'string' => '123.45']
],
'Scaling with small negative number' => [
'value' => -0.12345,
'scale' => 4,
'withScale' => 4,
'expected' => ['float' => -0.1234, 'string' => '-0.1234']
'Positive value with precision reduction to three' => [
'value' => 10.5555,
'scale' => 3,
'expected' => ['float' => 10.555, 'string' => '10.555']
]
];
}
Expand Down
Loading

0 comments on commit a06deec

Please sign in to comment.