Skip to content

Large number is truncated by NumberToLocalizedStringTransformer #26795

Closed
@BenoitDuffez

Description

@BenoitDuffez
Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no
Symfony version 3.3.9

Context

I have a form based off of an entity that has a big int field:

/**
 * @var integer
 *
 * @ORM\Column(name="ticket_id", type="bigint")
 */
private $ticketId;

MySQL is the database engine and when entering manual queries it works perfectly.

The form is built with this type:

/**
 * {@inheritdoc}
 */
public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('ticketId', IntegerType::class, ['scale' => 16]);
}

Note that I also tried the one argument add method with exactly the same result.

The issue

Now when I submit the form with an input of 201803221011791 it is saved as 201803221011790.

Analysis

I have debugged the code to this portion of NumberToLocalizedStringTransformer:184 where the data is cast as a float:

if (is_int($result) && $result === (int) $float = (float) $result) {
    $result = $float;
}

This looks like a float but it seems that PHP handles that as a double internally:

$ php -r '$result = 201803221011791; var_dump((float) $result);'
Command line code:1:
double(2.0180322101179E+14)

The precision is not lost yet:

$ php -r 'printf("%d\n", (float) 201803221011791);'
201803221011791

So later on in the round method line 244, it is parsed as a string (scale is 0 so the roundingCoef is 1):

// shift number to maintain the correct scale during rounding
$roundingCoef = pow(10, $this->scale);
// string representation to avoid rounding errors, similar to bcmul()
$number = (string) ($number * $roundingCoef);

So from now on number is 2:

$ php -r 'printf("%d\n", (string)((float) 201803221011791 * 1));'
2

Later on line 259 (roundingMode is ROUND_DOWN):

$number = $number > 0 ? floor($number) : ceil($number);

So basically we are doing this:

$ php -r 'printf("%d\n", floor((string)(float) 201803221011791));'
201803221011790

Which as you can see causes the last digit to be wrong.

Additional remark

The IntegerType uses IntegerToLocalizedStringTransformer, but the constructor erases the scale option, and calls the parent constructor with scale=0 hard-coded:

/**
 * Constructs a transformer.
 *
 * @param int  $scale        Unused
 * @param bool $grouping     Whether thousands should be grouped
 * @param int  $roundingMode One of the ROUND_ constants in this class
 */
public function __construct($scale = 0, $grouping = false, $roundingMode = self::ROUND_DOWN)
{
    if (null === $roundingMode) {
        $roundingMode = self::ROUND_DOWN;
    }

    parent::__construct(0, $grouping, $roundingMode);
}

The PHPDoc mentions the scale parameter is ignored, but this is not documented here: https://symfony.com/doc/3.4/reference/forms/types/number.html#scale

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