Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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

Sign up
Appearance settings

[Form] Improve rounding precision#21769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
jonny-no1 wants to merge3 commits intosymfony:2.7fromjonny-no1:issue-21734

Conversation

@jonny-no1
Copy link

@jonny-no1jonny-no1 commentedFeb 26, 2017
edited
Loading

QA
Branch?2.7
Bug fix?yes
New feature?no
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets21734
LicenseMIT
Doc PR

For full details see#21734 from#21734 (comment) onwards.

Excerpt:

$number =floor(37.37*100);// double(3736)var_dump($number);$number =floor(bcmul(37.37,100));// double(3737)var_dump($number);

Fromhttp://php.net/manual/en/language.types.float.php#language.types.float:

Additionally, rational numbers that are exactly representable as floating point numbers in base 10, like 0.1 or 0.7, do not have an exact representation as floating point numbers in base 2, which is used internally, no matter the size of the mantissa. Hence, they cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9999999999999991118....

So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.

@theofidry
Copy link
Contributor

To apply that change it means the BCMath extension must be enabled. I don't expect to be an issue as it's shipped by default in most PHP installations, but it might be good to either makebcmath required in thecomposer.json or add a polyfill.

@xabbuhxabbuh added this to the2.7 milestoneFeb 26, 2017
@javiereguiluz
Copy link
Member

@theofidry sadly anything that starts with"this just needs a simple PHP extension enabled..." is an issue. Requiring a whole PHP extension for this small feature is a no-go ... so we should go for a polyfill ... or enable this feature conditionally if BCmath is available?

@jonny-no1
Copy link
Author

I suggest we enable this conditionally as it's covering an edge case. Something along the lines of:

$number =function_exists('bcmul') ?bcmul($number,$roundingCoef,6) :$number *$roundingCoef;

or

if (function_exists('bcmul')) {$number =bcmul($number,$roundingCoef,6);}else {$number *=$roundingCoef;}

We could then isolate the test and skip it if BC Math isn't available.

@nicolas-grekas
Copy link
Member

This works without bcmath:

--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php@@ -265,34 +265,34 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface     {         if (null !== $this->precision && null !== $this->roundingMode) {             // shift number to maintain the correct scale during rounding-            $roundingCoef = pow(10, $this->precision);+            $roundingCoef = pow(10, $this->precision + 1);             $number *= $roundingCoef;              switch ($this->roundingMode) {                 case self::ROUND_CEILING:-                    $number = ceil($number);+                    $number = ceil(ceil($number) / 10);                     break;                 case self::ROUND_FLOOR:-                    $number = floor($number);+                    $number = floor(floor($number) / 10);                     break;                 case self::ROUND_UP:-                    $number = $number > 0 ? ceil($number) : floor($number);+                    $number = $number > 0 ? ceil(ceil($number) / 10) : floor(floor($number) / 10);                     break;                 case self::ROUND_DOWN:-                    $number = $number > 0 ? floor($number) : ceil($number);+                    $number = $number > 0 ? floor(floor($number) / 10) : ceil(ceil($number) / 10);                     break;                 case self::ROUND_HALF_EVEN:-                    $number = round($number, 0, PHP_ROUND_HALF_EVEN);+                    $number = round(round($number, 0, PHP_ROUND_HALF_EVEN) / 10, 0, PHP_ROUND_HALF_EVEN);                     break;                 case self::ROUND_HALF_UP:-                    $number = round($number, 0, PHP_ROUND_HALF_UP);+                    $number = round(round($number, 0, PHP_ROUND_HALF_UP) / 10, 0, PHP_ROUND_HALF_UP);                     break;                 case self::ROUND_HALF_DOWN:-                    $number = round($number, 0, PHP_ROUND_HALF_DOWN);+                    $number = round(round($number, 0, PHP_ROUND_HALF_DOWN) / 10, 0, PHP_ROUND_HALF_DOWN);                     break;             }-            $number /= $roundingCoef;+            $number /= $roundingCoef / 10;         }          return $number;

@dmaicher
Copy link
Contributor

dmaicher commentedFeb 28, 2017
edited
Loading

@nicolas-grekas I don't think this solves the issue 😉 It may work for the37.37 case but due to the limited precision offloat numbers it fails for other numbers.

Example test case that fails with your code insideNumberToLocalizedStringTransformerTest::reverseTransformWithRoundingProvider:

array(2,'2.01',2.01, NumberToLocalizedStringTransformer::ROUND_DOWN),

Gives:

There was 1 failure:1) Symfony\Component\Form\Tests\Extension\Core\DataTransformer\NumberToLocalizedStringTransformerTest::testReverseTransformWithRounding with data set #33 (2, '2.01', 2.0099999999999998, 2)Failed asserting that 2.0 matches expected 2.0099999999999998.

I found this case using a little script:

<?php$i =1;while(true) {$i +=0.01;$i = (string)$i;$number =$i *1000;$floored =floor($number);var_dump($i);var_dump($number);var_dump($floored);if ((string)$floored !== (string)$number) {echo"FAIL!" .PHP_EOL;        die;    }echo'----------' .PHP_EOL;}

==>

...string(4) "2.01"float(2010)float(2009)FAIL!

I don't think this can be solved without using a calculation with "arbitrary precision" like bcmath.

jonny-no1 reacted with thumbs up emojijonny-no1 reacted with heart emoji

@dmaicher
Copy link
Contributor

dmaicher commentedFeb 28, 2017
edited
Loading

Interesting is that this seems to work as well:

--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php@@ -242,7 +242,7 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface         if (null !== $this->scale && null !== $this->roundingMode) {             // shift number to maintain the correct scale during rounding             $roundingCoef = pow(10, $this->scale);-            $number *= $roundingCoef;+            $number = (string) ($number * $roundingCoef);              switch ($this->roundingMode) {                 case self::ROUND_CEILING:

So far I could not find a case where it fails 😋

I guess this is quite similar to thebcmul solution as it stores the result of the multiplication as a string to avoid precision issues?

xabbuh and kissifrot reacted with hooray emoji

@nicolas-grekas
Copy link
Member

nicolas-grekas commentedMar 2, 2017
edited
Loading

@foaly-nr1 would you update you PR using@dmaicher's patch, and add the test case he's suggesting also?

@jonny-no1
Copy link
Author

@nicolas-grekas on it.

dmaicher reacted with thumbs up emoji

// shift number to maintain the correct scale during rounding
$roundingCoef =pow(10,$this->precision);
$number*=$roundingCoef;
$number= (string) ($number *$roundingCoef);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Maybe also add a comment here why we chose the string representation?

@jonny-no1
Copy link
Author

Just rebased but tests are still failing as the 2.7 build is currently broken.

@fabpot
Copy link
Member

Thank you @foaly-nr1.

fabpot added a commit that referenced this pull requestMar 2, 2017
This PR was squashed before being merged into the 2.7 branch (closes#21769).Discussion----------[Form] Improve rounding precision| Q             | A| ------------- | ---| Branch?       | 2.7| Bug fix?      | yes| New feature?  | no| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets | 21734| License       | MIT| Doc PR        |For full details see#21734 from#21734 (comment) onwards.Excerpt:```php$number = floor(37.37*100); // double(3736)var_dump($number);$number = floor(bcmul(37.37, 100)); // double(3737)var_dump($number);```Fromhttp://php.net/manual/en/language.types.float.php#language.types.float:> Additionally, rational numbers that are exactly representable as floating point numbers in base 10, like 0.1 or 0.7, do not have an exact representation as floating point numbers in base 2, which is used internally, no matter the size of the mantissa. Hence, they cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9999999999999991118....>> So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.Commits-------e50804c [Form] Improve rounding precision
@fabpotfabpot closed thisMar 2, 2017
@fabpot
Copy link
Member

Thanks@dmaicher for the nice trick!

dmaicher, xabbuh, HeahDude, and sstok reacted with thumbs up emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

1 more reviewer

@dmaicherdmaicherdmaicher left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

2.7

Development

Successfully merging this pull request may close these issues.

8 participants

@jonny-no1@theofidry@javiereguiluz@nicolas-grekas@dmaicher@fabpot@xabbuh@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp