Skip to content

MoneyType with divisor: integer conversion leads to wrong database values #21026

@elmariachi111

Description

@elmariachi111
Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no
Symfony version 2.8.14

http://stackoverflow.com/questions/41286474/symfony2-moneytype-with-divisor-integer-conversion-leads-to-wrong-database-valu

We're storing all our money related values as cents in our database (ODM but ORM will likely behave the same). We're using MoneyType to convert user facing values (12,34€) into their cents representation (1234c). The typical float precision problem arises here: due to insufficient precision there are many cases that create rounding errors that are merely visible when debugging. MoneyType will convert incoming strings to floats that may be not precise ("1765" => 1764.9999999998). Things get bad as soon as you persist these values. The (integer) cast in IntType (ODM/ORM) will strip off the value's mantissa instead of rounding the value, effectively leading to writing wrong values into the database (1764 instead of 1765 when "1765" is internally 1764.9999999998).

Here's a unit test that should work from within any Symfony/container application:

//for better debugging: set ini_set('precision', 17); 

class PrecisionTest extends WebTestCase
{
    private function buildForm() {
        $builder = $this->getContainer()->get('form.factory')->createBuilder(FormType::class, null, []);
        $form = $builder->add('money', MoneyType::class, [
            'divisor' => 100
        ])->getForm();
        return $form;
    }

    // high-level symptom
    public function testMoneyType() {
        $form = $this->buildForm();
        $form->submit(['money' => '12,34']);

        $data = $form->getData();

        $this->assertEquals(1234, $data['money']);
        $this->assertEquals(1234, (int)$data['money']);

        $form = $this->buildForm();
        $form->submit(['money' => '17,65']);

        $data = $form->getData();

        $this->assertEquals(1765, $data['money']);
        $this->assertEquals(1765, (int)$data['money']); //fails: data[money] === 1764 
    }

    //root cause
    public function testParsedIntegerPrecision() {

        $string = "17,65";
        $transformer = new MoneyToLocalizedStringTransformer(2, false,null, 100);
        $value = $transformer->reverseTransform($string);

        $int = (integer) $value;
        $float = (float) $value;

        $this->assertEquals(1765, (float)$float);
        $this->assertEquals(1765, $int); //fails: $int === 1764
    }
}

might relate to #10240 and #8473

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions