Search code examples
phproundingfloorceil

PHP calculate integer of division


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)


Solution

  • 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