2

I need to round prices for my client in a non-common way :

120.28 => 120.2

130.23 => 130.2

150.64 => 150.6

The rule is to always keep the nearest lowest 1 precision.

I try to use the round() method but all my tries are useless since this method handle half which i have to ignore.

ceil() and floor() will not work out of the box.

Is a combination of those 3 methods would work ?

Greco Jonathan
  • 2,517
  • 2
  • 29
  • 54
  • 2
    How about `0.1 * intval(10 * $price)`. – KIKO Software Oct 21 '22 at 10:06
  • @KIKOSoftware yes it's working can you elaborate an answer and explain how it's doing this ? – Greco Jonathan Oct 21 '22 at 10:08
  • but it may not be enougth, 150.25 with this will end to 150.20000000000002, a final round precision 2 will fix that => round(0.1 * intval(10 * $price), 2) – Greco Jonathan Oct 21 '22 at 10:09
  • Why isn't [this](https://stackoverflow.com/q/38350328/2943403) / [this](https://stackoverflow.com/questions/4622328/how-can-i-format-the-number-for-only-showing-1-decimal-place-in-php) suitable? – mickmackusa Oct 21 '22 at 12:49
  • 1
    And [Round floats decimals to nearest tenth in PHP](https://stackoverflow.com/q/39435654/2943403) and [Show only one decimal place or nothing of a floating point number](https://stackoverflow.com/q/39922253/2943403) – mickmackusa Oct 21 '22 at 13:07
  • @GrecoJonathan You might want to look at https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency regarding `float` for currency values. – Progman Oct 21 '22 at 16:39

2 Answers2

2

I think the solution 0.1 * intval(10 * $price) causes problems when prices are the result of calculations.

$price = 0.70 + 0.10;  //0.80 expected
print_r(0.1 * intval(10 * $price));  //0.7

The following solution avoids this error by rounding to 2 decimal places and truncating using a string function.

$price = 0.70 + 0.10;  //0.80 expected
$newPrice = substr(sprintf('%0.2f',$price),0,-1);
print_r($newPrice);  //0.8

Demo: https://3v4l.org/hFjDL

jspit
  • 7,276
  • 1
  • 9
  • 17
  • Ah, yes, a very good point. 0.70 + 0.10 just happens to be slightly less than 0.80 in their floating point representation. It therefore becomes 0.7. Treat the floating number as a string and the problem doesn't exist. – KIKO Software Oct 21 '22 at 12:12
  • There's still a minor problem with your solution. We logically assume that the number roughly has 2 decimals, with some noise in the nth decimal. However, if there's more than a bit of noise your routine falters. So if the price is 0.896 then the result is 0.9. See: https://3v4l.org/9Ekfc – KIKO Software Oct 21 '22 at 12:20
  • The inaccuracy for the present example is 1.E-15. In order to get significant deviations in the 3rd digit, however, approx. 10**12 operations of the above type are necessary. This is much more than a little noise. – jspit Oct 22 '22 at 09:15
  • I agree, it's a bit extreme. But it can easily occur on multiplications and divisions. – KIKO Software Oct 22 '22 at 09:24
  • Therefore, floating point should not be used when exact results are required, such as in commercial calculations. – jspit Oct 22 '22 at 10:42
1

phpHow about 0.1 * intval(10 * $price)?

What it does is multiply the price by 10, so 120.28 becomes 1202.8. Then it takes integer part using intval(), which is 1202, and then divides it by ten giving 120.2.

See: https://3v4l.org/3qMhd

I think the problem indicated by jspit is relevant, we could compensate for prices that are just a hair under their expected value. Something like this:

0.1 * (int)(10 * $price + 1E-6))

It works, see https://3v4l.org/5YCEu.

The value 1E-6 is chosen with care, not too much or too little.

floating points in PHP have roughly 16 digits of precision and money is usually not more than a few billion. Billions use 9 or 10 digits, subtract that from the 16 we have and you get the 6.

Also, 6 digits should be ample. If an amount is 10.599999 it will still be converted to 10.5. Only when it is even closer, say 10.5999999 will it become 10.6.

You may want to put this point somewhere else. You could decide that 10.555 should be 10.6. Then you choose a value of 0.45. See; https://3v4l.org/YWVI0. The point is that you can exactly choose where you want this point to be, no more surprises.

KIKO Software
  • 15,283
  • 3
  • 18
  • 33
  • really thank you for this trick, you should check my other comments – Greco Jonathan Oct 21 '22 at 10:13
  • 1
    @GrecoJonathan If you check my example code you'll see it doesn't happen to me, no rounding is needed. However, the reason for your problem is float precision. When outputing floats, with a known number of decimals, you should use [number_format()](https://www.php.net/manual/en/function.number-format), do not use `round()` for this. – KIKO Software Oct 21 '22 at 10:16
  • intval should not be used anymore can we use (int) (10 * float) instead ? Safely ? – Greco Jonathan Oct 21 '22 at 10:18
  • 1
    @GrecoJonathan Yes, that would perform the same task. A lot of systems, that deal with money, avoid precision problems by using an integer for money. So 150.25 is simply 15025. Then only when displaying this amount do they insert the decimal point. – KIKO Software Oct 21 '22 at 10:20
  • I would not quickly use the words: _"should not be used anymore"_. `intval()` is a valid function, and is not going to be deprecated in any way soon. Suggesting otherwise should at least come with a reason or reference. – KIKO Software Oct 21 '22 at 10:22
  • I think the solution `0.1 * intval(10 * $price)` causes problems when prices are the result of calculations. See my answer. – jspit Oct 21 '22 at 11:58