brick / money

A money and currency library for PHP
MIT License
1.65k stars 102 forks source link

Simple rounding test failed #54

Closed palansher closed 2 years ago

palansher commented 2 years ago

Hello!

I am trying to check the corrections of rounding. This simple test must be passed:

1/7/13713 = 1

But result discourages:

$brickRoundings=[
    RoundingMode::UP,
    RoundingMode::DOWN,
    RoundingMode::CEILING,
    RoundingMode::FLOOR,
    RoundingMode::HALF_UP,
    RoundingMode::HALF_DOWN,
    RoundingMode::HALF_CEILING,
    RoundingMode::HALF_FLOOR,
    RoundingMode::HALF_EVEN,
];
$money = Brick::of(1, 'EUR');
foreach ($brickRoundings as $brickRounding) {
    $brickResult=$money->dividedBy(7, $brickRounding)->dividedBy(13, $brickRounding)
        ->multipliedBy(7, $brickRounding)->multipliedBy(13, $brickRounding);
    echo 'brick: ' . $brickResult->formatTo('en_US').PHP_EOL;
}
brick: €1.82
brick: €0.91
brick: €1.82
brick: €0.91
brick: €0.91
brick: €0.91
brick: €0.91
brick: €0.91
brick: €0.91

the result is not 1 while even Windows 10 calculator app provides the correct answer =1. Excel, for sure also .. also PHP:

<?php
echo 1/7/13*7*13;
// =1

Would you please explain: why I cannot get the correct answer = 1?

thank you!

palansher commented 2 years ago

It seems I found right way using toRational():


$brickRoundings=[
    RoundingMode::UP,
    RoundingMode::DOWN,
    RoundingMode::CEILING,
    RoundingMode::FLOOR,
    RoundingMode::HALF_UP,
    RoundingMode::HALF_DOWN,
    RoundingMode::HALF_CEILING,
    RoundingMode::HALF_FLOOR,
    RoundingMode::HALF_EVEN,
];

$brickMoney = Brick::of(1, 'EUR');
foreach ($brickRoundings as $brickRounding) {
    $brickMoney->toRational()
    ->dividedBy(7, $brickRounding)
    ->dividedBy(13, $brickRounding)
    ->multipliedBy(7, $brickRounding)
    ->multipliedBy(13, $brickRounding)    
    ->to($brickMoney->getContext(), RoundingMode::UNNECESSARY)->formatTo('en_US');
    echo 'brick: ' . $brickMoney. PHP_EOL;
}

now the result is correct and =1 with and without rounding :

brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00
brick: EUR 1.00

So, you can close the ticket if you have no comments. Thanks for the good tool!

BenMorel commented 2 years ago

Hi, glad you figured it out! Yes of course, if you apply roundings along the way, you'll end up with the incorrect amount at the end. As you've discovered, RationalMoney is the way to go for exact calculations! :+1: