How to Ceil, Floor and Round Bcmath Numbers

How to ceil, floor and round bcmath numbers?

After a night lost trying to solve this problem I believe I've found a rather simple solution, here it is:

function bcceil($number)
{
if (strpos($number, '.') !== false) {
if (preg_match("~\.[0]+$~", $number)) {
return bcround($number, 0);
}

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

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

function bcfloor($number)
{
if (strpos($number, '.') !== false) {
if (preg_match("~\.[0]+$~", $number)) {
return bcround($number, 0);
}

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

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

function bcround($number, $precision = 0)
{
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);
}

return $number;
}

I think I didn't miss anything, if someone can spot any bug please let me know. Here are some tests:

assert(bcceil('4') == ceil('4')); // true
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') == floor('4')); // 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', 0) == number_format('3', 0)); // 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

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

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

PHP BC Math library ignore rounding rules

Thank you Christos Lytras for pointing what I did wrong. Because I'm using BCMath calculations in multiple classes and I don't have enough time to rewrite all places with floats to integers, I decided to create simple trait. It solves all my problems with rounded values. Here is trait code:

trait FloatCalculationsTrait
{

/**
* Default precision for function results
*
* @var integer
*/
protected $scale = 2;

/**
* Default precision for BCMath functions
*
* @var integer
*/
protected $bcMathScale = 10;

/**
* Rounding calculation values, based on https://stackoverflow.com/a/60794566/3212936
*
* @param string $valueToRound
* @param integer|null $scale
* @return float
*/
protected function round(string $valueToRound, ?int $scale = null): float
{
if ($scale === null) {
$scale = $this->scale;
}

$result = $valueToRound;

if (strpos($valueToRound, '.') !== false) {
if ($valueToRound[0] != '-') {
$result = bcadd($valueToRound, '0.' . str_repeat('0', $scale) . '5', $scale);
} else {
$result = bcsub($valueToRound, '0.' . str_repeat('0', $scale) . '5', $scale);
}
}

return $result;
}

/**
* Add floats
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function add(?float $firstElement, ?float $secondElement, ?int $scale = null): float
{
$result = bcadd($firstElement, $secondElement, $this->bcMathScale);

return $this->round($result, $scale);
}

/**
* Substract floats
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function substract(?float $firstElement, ?float $secondElement, ?int $scale = null): float
{
$result = bcsub($firstElement, $secondElement, $this->bcMathScale);

return $this->round($result, $scale);
}

/**
* Alias for `substract` function
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function sub(?float $firstElement, float $secondElement, ?int $scale = null): float
{
return $this->substract($firstElement, $secondElement, $scale);
}

/**
* Multiply floats
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function multiply(?float $firstElement, ?float $secondElement, ?int $scale = null): float
{
$result = bcmul($firstElement, $secondElement, $this->bcMathScale);

return $this->round($result, $scale);
}

/**
* Alias for `multiply` function
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function mul(?float $firstElement, ?float $secondElement, ?int $scale = null): float
{
return $this->multiply($firstElement, $secondElement, $scale);
}

/**
* Divide floats
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function divide(?float $firstElement, ?float $secondElement, ?int $scale = null): float
{
$result = bcdiv($firstElement, $secondElement, $this->bcMathScale);

return $this->round($result, $scale);
}

/**
* Alias for `divide` function
*
* @param float|null $firstElement
* @param float|null $secondElement
* @param integer|null $scale
* @return float
*/
protected function div(?float $firstElement, ?float $secondElement, ?int $scale = null): float
{
return $this->divide($firstElement, $secondElement, $scale);
}
}

And here you can check results: http://sandbox.onlinephpfunctions.com/code/5b602173a1825a2b2b9f167a63646477c5105a3c

floor and ceil with number of decimals

Neither Python built-in nor numpy's version of ceil/floor support precision.

One hint though is to reuse round instead of multiplication + division (should be much faster):

def my_ceil(a, precision=0):
return np.round(a + 0.5 * 10**(-precision), precision)

def my_floor(a, precision=0):
return np.round(a - 0.5 * 10**(-precision), precision)

UPD:
As pointed out by @aschipfl, for whole values np.round will round to the nearest even, which will lead to unexpected results, e.g. my_ceil(11) will return 12. Here is an updated solution, free of this problem:

def my_ceil(a, precision=0):
return np.true_divide(np.ceil(a * 10**precision), 10**precision)

def my_floor(a, precision=0):
return np.true_divide(np.floor(a * 10**precision), 10**precision)

Getting the modulo of two real numbers with BCMath

I think it can be done using some math:

You can relate mod(a,b) to a and b with this equation:

a = b * floor(a/b) + mod(a,b)

(explanation)

and then solve that equation for mod to yield

mod(a,b) = a - b * floor(a/b)

Substituting your numbers you get

mod(10,9.2) = 10 - 9.2 * floor(10/9.2) = 0.8

Also see here for a bcmath floor implementation: How to ceil, floor and round bcmath numbers?

SAS ceil/floor issues using big numbers and wanting to ceil/floor to the nearest 10,000

CEIL and FLOOR only remove decimals - specifically rounding to integer value. If you want it rounded to (above/below) multiple of 10,000, you have to do it a bit more complicatedly:

S1CovA_ceil = ceil(s1covA/10000)*10000;

And the same for floor. Basically you have to divide it by the desired rounding level, round the rest with ceil/floor, and then multiply back.

Unfortunately, as far as I'm aware, SAS doesn't allow rounding in a particular direction except for straight integer rounding.

bcmath sometimes returns float, and sometimes round result

Have you set the scale using bcscale? If it is set to 0, it could explain the behaviour. Please, try to call bcscale(100) before your computation;



Related Topics



Leave a reply



Submit