How to round/ceil/floor a bcmath number in PHP?
Asked Answered
A

3

11

Is there any library function for this purpose, so I don't do it by hand and risk ending in TDWTF?

echo ceil(31497230840470473074370324734723042.6);

// Expected result
31497230840470473074370324734723043

// Prints
<garbage>
Afterburning answered 23/10, 2008 at 19:15 Comment(1)
See also my related question: #1643114Stooge
T
7

This will work for you:

$x = '31497230840470473074370324734723042.9';

bcscale(100);
var_dump(bcFloor($x));
var_dump(bcCeil($x));
var_dump(bcRound($x));

function bcFloor($x)
{
    $result = bcmul($x, '1', 0);
    if ((bccomp($result, '0', 0) == -1) && bccomp($x, $result, 1))
        $result = bcsub($result, 1, 0);

    return $result;
}

function bcCeil($x)
{
    $floor = bcFloor($x);
    return bcadd($floor, ceil(bcsub($x, $floor)), 0);
}

function bcRound($x)
{
    $floor = bcFloor($x);
    return bcadd($floor, round(bcsub($x, $floor)), 0);
}

Basically it finds the flooy by multiplying by one with zero precision.

Then it can do ceil / round by subtracting that from the total, calling the built in functions, then adding the result back on

Edit: fixed for -ve numbers

Trimetric answered 23/10, 2008 at 19:43 Comment(2)
+1, but it might be worth adding a scale argument to bcCeil and bcRound, as the behaviour is dependent on the scale. If you call bcscale(0) then try bcCeil('1.1') you're going to get '1' not '2' as you might expect. Allowing the scale to be specified would be consistent with the other BCMath functions.Paddie
Also to note, the scale argument should be default null and should not overwrite the value set by bcscale if not provided.Paddie
S
13

UPDATE: See my improved answer here: How to ceil, floor and round bcmath numbers?.


These functions seem to make more sense, at least to me:

function bcceil($number)
{
    if ($number[0] != '-')
    {
        return bcadd($number, 1, 0);
    }

    return bcsub($number, 0, 0);
}

function bcfloor($number)
{
    if ($number[0] != '-')
    {
        return bcadd($number, 0, 0);
    }

    return bcsub($number, 1, 0);
}

function bcround($number, $precision = 0)
{
    if ($number[0] != '-')
    {
        return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
    }

    return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}

They support negative numbers and the precision argument for the bcround() function.

Some tests:

assert(bcceil('4.3') == ceil('4.3')); // true
assert(bcceil('9.999') == ceil('9.999')); // true
assert(bcceil('-3.14') == ceil('-3.14')); // true

assert(bcfloor('4.3') == floor('4.3')); // true
assert(bcfloor('9.999') == floor('9.999')); // true
assert(bcfloor('-3.14') == floor('-3.14')); // true

assert(bcround('3.4', 0) == number_format('3.4', 0)); // true
assert(bcround('3.5', 0) == number_format('3.5', 0)); // true
assert(bcround('3.6', 0) == number_format('3.6', 0)); // true
assert(bcround('1.95583', 2) == number_format('1.95583', 2)); // true
assert(bcround('5.045', 2) == number_format('5.045', 2)); // true
assert(bcround('5.055', 2) == number_format('5.055', 2)); // true
assert(bcround('9.999', 2) == number_format('9.999', 2)); // true
Stooge answered 31/10, 2009 at 9:0 Comment(1)
Won't work with integers. Good realisation of these functions here: https://mcmap.net/q/376089/-how-to-ceil-floor-and-round-bcmath-numbers Alix, you can edit your post to link to the newer one.Zebrass
T
7

This will work for you:

$x = '31497230840470473074370324734723042.9';

bcscale(100);
var_dump(bcFloor($x));
var_dump(bcCeil($x));
var_dump(bcRound($x));

function bcFloor($x)
{
    $result = bcmul($x, '1', 0);
    if ((bccomp($result, '0', 0) == -1) && bccomp($x, $result, 1))
        $result = bcsub($result, 1, 0);

    return $result;
}

function bcCeil($x)
{
    $floor = bcFloor($x);
    return bcadd($floor, ceil(bcsub($x, $floor)), 0);
}

function bcRound($x)
{
    $floor = bcFloor($x);
    return bcadd($floor, round(bcsub($x, $floor)), 0);
}

Basically it finds the flooy by multiplying by one with zero precision.

Then it can do ceil / round by subtracting that from the total, calling the built in functions, then adding the result back on

Edit: fixed for -ve numbers

Trimetric answered 23/10, 2008 at 19:43 Comment(2)
+1, but it might be worth adding a scale argument to bcCeil and bcRound, as the behaviour is dependent on the scale. If you call bcscale(0) then try bcCeil('1.1') you're going to get '1' not '2' as you might expect. Allowing the scale to be specified would be consistent with the other BCMath functions.Paddie
Also to note, the scale argument should be default null and should not overwrite the value set by bcscale if not provided.Paddie
D
0

OK, for my high-precision Money library, which is currently on hundreds of production sites, I had to completely rewrite this bcround functionality. Nothing I found on the entire Internet was up to code.

Here's what I came up with:

/**
 * Based off of https://mcmap.net/q/376089/-how-to-ceil-floor-and-round-bcmath-numbers
 * Thanks, [Alix Axel](https://stackoverflow.com/users/89771/alix-axel)!
 *
 * @param $number
 * @param int $precision
 * @return string
 */
function bcround($number, $precision = BCMathCalcStrategy::PRECISION)
{
    if (strpos($number, '.') !== false) {
        if ($number[0] != '-') return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
        return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
    }

    // Pad it out to the desired precision.
    return number_format($number, $precision);
}
Duval answered 10/4, 2019 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.