0
numfmt_format_currency(new NumberFormatter('en',2), 31.005, "USD")

This code outputs $31.00. But it should output $31.01.

It appears to me that PHP is ignoring to convert 0.005 to 0.01 when it comes to price format.

I was trying to fix Magento's currency format issue and I'm running into this problem on the PHP side.

hakre
  • 193,403
  • 52
  • 435
  • 836
Gullible
  • 3
  • 2
  • try `number_format()` – Eng Cy Aug 03 '23 at 07:10
  • 3
    `NumberFormatter()` has a number of rounding options. A note elsewhere suggests that it defaults to `ROUND_HALFEVEN`, so it rounds towards the even neighbour. I read that as meaning that your example code for 31.015 would round to 31.02. You could change it to `ROUND_HALFUP` which on the face of it seems like a more sensible default. – droopsnoot Aug 03 '23 at 07:26
  • @droopsnoot The `ROUND_HALFEVEN` mode (aka "banker's rounding") is [commonly used](https://stackoverflow.com/questions/45223778/is-bankers-rounding-really-more-numerically-stable). – Olivier Aug 03 '23 at 07:54
  • As a side note, I found that in JS `Intl.NumberFormat()` the default is the `ROUND_HALFUP` equivalent ({[`roundingMode : "halfExpand"}`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingmode)). – hakre Aug 03 '23 at 10:16

1 Answers1

2

31.005 is actually between two numbers, see Floating Point Precision on https://php.net/float .

> ini_set('precision', 23)
= "14"

> 31.005
= 31.00499999999999900524

(There is no number 31.005 in PHP -- and other languages.)

You need to provide the number with enough precision but as little error as necessary to match your display requirements.

This works by specifying the rounding mode, when you want 31.005 to be rounded to 31.01, that is up or halfup.

$formatter = new NumberFormatter('en', NumberFormatter::CURRENCY);

$formatter->setAttribute(
    NumberFormatter::ROUNDING_MODE, 
    NumberFormatter::ROUND_HALFUP
);


echo $formatter->formatCurrency(31.005, 'USD'), "\n";
# Output: $31.01

This is what droopsnoot commented about.

Number Formatter Default Mode and Creation

As Olivier reminded in a comment, the default mode is halfeven, also known as banker's rounding.

Also they remark that it "would be cleaner to use explicitly" the class constant NumberFormatter::CURRENCY over of the magic number 2.

$formatter = new NumberFormatter('en', 2);
$formatter = new NumberFormatter('en', NumberFormatter::CURRENCY);

This will make your code communicate better, otherwise the 2 might be read as number of digits or precision, which is not the case here. You can find all style constants in the PHP manual.


Remark: This is nothing PHP specific, when you pass those numbers to javascript and then need to format them in Javascript, you run into the same.

Cf. Answer to "Rounding in JavaScript does not give the same result as in PHP?".

hakre
  • 193,403
  • 52
  • 435
  • 836
  • 1
    By default, the `ROUND_HALFEVEN` mode is used (it's known as [banker's rounding](https://stackoverflow.com/questions/45223778/is-bankers-rounding-really-more-numerically-stable)). – Olivier Aug 03 '23 at 07:51
  • 1
    It would be cleaner to use explicitly `NumberFormatter::CURRENCY` instead of `2`. – Olivier Aug 03 '23 at 07:53
  • @Olivier: Thanks for these insightful remarks, I've edited the answer. – hakre Aug 03 '23 at 08:04
  • Thanks for such a wonderful explanation. It worked for me! Thank You @hakre – Gullible Aug 04 '23 at 06:51