Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Form] NumberType fields do not round as advertised when no scale is defined #58204

Copy link
Copy link
Open
@pacproduct

Description

@pacproduct
Issue body actions

Symfony version(s) affected

7.1, 6.4, 5.4

Description

According to the official documentation on how NumberType fields round float numbers, the default rounding mode is \NumberFormatter::ROUND_HALFUP.

When no scale is defined though, I'm not experiencing the advertised behavior and the following happens:

  1. Numbers typed-in by users do not get rounded at all: they are provided as is to the application when submitting the form.
  2. When loaded back into the form, they get rounded "half even", not "half up" (e.g. number 1.2345 would get rounded to 1.234 instead of 1.235).

Maybe I'm missing something, but I would expect the typed-in numbers to be rounded "half up" as advertised by the documentation, in both cases above, whether I set a scale or not.


Initially, I thought it might have been a documentation issue (cf symfony/symfony-docs#20134), but I'm leaning towards a core Symfony issue instead now.

How to reproduce

Here is a reproducer repository that illustrates the described behavior:

https://github.com/pacproduct/SymfonyNumberTypeRoudingIssue

Possible Solution

I'm not sure I'd know how to cleanly fix this.
But as a starting lead: I believe the following functions may be the source of these 2 behaviors:


\Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer::round:

Seems like this function is the one that applies the rounding mode before numbers are returned by the form. It only applies the rounding mode if a scale is defined:

    private function round(int|float $number): int|float
    {
        if (null !== $this->scale) {
            [...]

            $number = match ($this->roundingMode) {
                \NumberFormatter::ROUND_CEILING => ceil($number),
                [...]
                \NumberFormatter::ROUND_HALFDOWN => round($number, 0, \PHP_ROUND_HALF_DOWN),
            };

            [...]
        }
        return $number;
    }

\Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer::getNumberFormatter:

Seems like this function is the one that applies the rounding mode before numbers are injected in the form. It only applies the rounding mode if a scale is defined:

    protected function getNumberFormatter(): \NumberFormatter
    {
        $formatter = new \NumberFormatter($this->locale ?? \Locale::getDefault(), \NumberFormatter::DECIMAL);

        if (null !== $this->scale) {
            $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale);
            $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode);
        }

        $formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->grouping);

        return $formatter;
    }

Additional Context

For information, the rounding mode observed here (\NumberFormatter::ROUND_HALFEVEN) is the default behavior of the native PHP library \NumberFormatter, which explains why not setting a rounding mode to it makes it behave the way it does in NumberToLocalizedStringTransformer::getNumberFormatter.

Illustration:

$formatter = new \NumberFormatter('en', \NumberFormatter::DECIMAL);
var_dump($formatter->format(485.3485)); // Outputs: string(7) "485.348"

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

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.