2

I would like to calculate an integer part of division. The numerator and denominator (especially their precision) should not be altered because it might change from one calculation to other, also the denominator is as an example, its integer part as well as decimal part might be different.

I tried to use floor, ceil, round but none of them produced a correct result. Please see the code below, perhaps you'll spot the error:

<?php

$valueArr = [
    // should return 1999
    199.90,
    199.92,
    199.95,
    199.97,

    // should return 2000
    200.00,
    200.02,
    200.05,
    200.07,

    // should return 2001
    200.10,
    200.12,
    200.15,
    200.17,
];

$denominator = 0.1;

$resultArr = [];

foreach ($valueArr as $value) {
    $key = (string) $value;
    $result = floor($value / $denominator);
    $resultArr[$key] = $result;
}

echo "Denominator:\n";
var_dump($denominator);
echo "\n";

print_r($resultArr);

that gives result:

Denominator:
float(0.1)

Array
(
    [199.9] => 1999
    [199.92] => 1999
    [199.95] => 1999
    [199.97] => 1999
    [200] => 2000
    [200.02] => 2000
    [200.05] => 2000
    [200.07] => 2000
    [200.1] => 2000
    [200.12] => 2001
    [200.15] => 2001
    [200.17] => 2001
)

where:

    [200.1] => 2000

is wrong because integer part of (200.1 / 0.1) is 2001.

Do you know how to produce correct result for the $valueArr as above? What did I do wrong?

I'm using PHP 7.3.8 (cli)

Jimmix
  • 5,644
  • 6
  • 44
  • 71
  • A more concise question might be: Why are these different? `var_dump(200.10/0.1); // float (2001)` and `var_dump(floor(200.10/0.1)); // float(2000)` – Jon Jul 31 '20 at 01:23

4 Answers4

1

I get the right results using bcdiv().

$result = bcdiv($value,$denominator);

I always use BcMath, seems more reliable to me.

Fred
  • 74
  • 4
  • it works but only if the denominator is small precision, I tried `echo bcdiv(0.020010, 0.00001);` but it gave `Warning: bcdiv(): Division by zero` do you know how to solve that? adding precision as the third parameter to bcdiv did not help. – Jimmix Jul 31 '20 at 02:16
  • 1
    @Jimmix The parser has already converted them to floats if you provide the numbers that way. Pass the input numbers as strings. e.g. `bcdiv('0.020010', '0.00001');` – EPB Jul 31 '20 at 02:36
  • @EPB Thank you, I was too tired, that's even mentioned in the documentation: `bcdiv ( string $dividend , string $divisor [, int $scale = 0 ] ) : string` where typing is `string` not a `float` – Jimmix Jul 31 '20 at 09:51
  • @EBP I've found also another way in case you need to deal with floats that are not literals but are coming from some calculations. `echo bcdiv(sprintf('%f', 0.020010), sprintf('%f', 0.00001));` I see now also that the problem was when casting/juggling to string `0.0001` that gave `string(6) "0.0001"` but casting/juggling to string `0.00001` gave `string(6) "1.0E-5"` that was not tolerated by `bcdiv` and resulted in the `Warning: bcdiv(): Division by zero` – Jimmix Jul 31 '20 at 21:42
1

So there's two issues you're going to run into here. First is the lack of precision on floating points in general, and the second is PHP's automatically coercing your inputs into floating points before you have a chance to use something like bcdiv.

As such: First step is to store your input numbers as strings so you're not losing precision out of the gate by the parser interpreting them as floating point numbers. Then use bcdiv on them.

Since you're just after the integer portion and bcdiv returns a string on success, we can just drop the decimal part with string functions.

<?php

$valueArr = [
    // should return 1999
    '199.90',
    '199.92',
    '199.95',
    '199.97',

    // should return 2000
    '200.00',
    '200.02',
    '200.05',
    '200.07',

    // should return 2001
    '200.10',
    '200.12',
    '200.15',
    '200.17',

    '381736192374124241.294',
];

$denominator = '0.1';

$resultArr = [];

foreach ($valueArr as $value) {
    $key = (string) $value;
    $result = explode('.', bcdiv($value, $denominator))[0];
    $resultArr[$key] = $result;
}

echo "Denominator:\n";
var_dump($denominator);
echo "\n";

print_r($resultArr);

And if you want to do something like rounding, ceil, floor with the output of bcdiv, check out this answer:

https://stackoverflow.com/a/51390451/395384

EPB
  • 3,939
  • 1
  • 24
  • 26
0

You did not do anything wrong. This is a problem with computers. It's difficult to represent float-point numbers accurately in a fixed space.

Try this

foreach ($valueArr as $v) {
    $resultArr []= floor($v * (1 / $denominator));
}

My advice would be to try to convert division operation into multiplication.

In your case, division by 0.1 === multiplication by 10. So, use that.

jack
  • 1,391
  • 6
  • 21
  • that unlucky will not work for opposite example ie `(0.019990 / 0.00001)` gives `1998` instead of `1999` and for `(0.02 / 0.00001)` gives `1999` instead of `2000` – Jimmix Jul 31 '20 at 02:03
0

In case you don't have the bcdiv that comes with the BcMath extenstion you may use the sprintf() function to achieve a proper result with the floor() and without any problem even in a case the denominator is a float smaller than 0.0001.

Instead of:

$result = floor($value / $denominator);

use this:

$result = floor(sprintf('%f', $value / $denominator));

and you'll get the proper:

[200.1] => 2001
Jimmix
  • 5,644
  • 6
  • 44
  • 71