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
Which Mime Type Should I Use for Mp3
Alter Table in Magento Setup Script Without Using SQL
What Is the Best Practice to Export Canvas with High Quality Images
Type Casting for User Defined Objects
Artisan Migrate Could Not Find Driver
How to Password Protect Folder/Page Using PHP Without a Db or Username
Creating Anonymous Objects in PHP
How Can One Run Multiple Versions of PHP 5.X on a Development Lamp Server
Trying to Get Laravel 5 Email to Work
Setting PHP Tmp Dir - PHP Upload Not Working
PHP Sessions with Disabled Cookies, Does It Work
Troubleshooting "Warning: Session_Start(): Cannot Send Session Cache Limiter - Headers Already Sent"
PHP String Function to Get Substring Before the Last Occurrence of a Character