如何对 bcmath 数字进行向上、向下和舍入?

发布于 2024-08-09 05:18:07 字数 710 浏览 5 评论 0原文

我需要模仿 ceil() 的确切功能,< a href="http://www.php.net/manual/en/function.floor.php" rel="noreferrer">floor() 和 round() bcmath 数字函数我已经找到了一个非常类似的问题,但不幸的是提供的答案对我来说不够好,因为它缺乏对负数的支持并且缺少round()函数的精度参数

我想知道是否有人可以针对这个问题提出一个相当简短而优雅的解决方案。

感谢所有意见,谢谢!

I need to mimic the exact functionality of the ceil(), floor() and round() functions on bcmath numbers, I've already found a very similar question but unfortunately the answer provided isn't good enough for me since it lacks support for negative numbers and the precision argument for the round() function is missing.

I was wondering if anyone can come up with a rather short and elegant solution to this problem.

All input is appreciated, thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

分开我的手 2024-08-16 05:18:07

经过一晚尝试解决这个问题后,我相信我找到了一个相当简单的解决方案,如下:

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;
}

我想我没有错过任何东西,如果有人能发现任何错误,请告诉我。以下是一些测试:

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

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
执笔绘流年 2024-08-16 05:18:07
function bcnegative($n)
{
    return strpos($n, '-') === 0; // Is the number less than 0?
}

function bcceil($n)
{
    return bcnegative($n) ? (($v = bcfloor(substr($n, 1))) ? "-$v" : $v)
                          : bcadd(strtok($n, '.'), strtok('.') != 0);
}

function bcfloor($n)
{
    return bcnegative($n) ? '-' . bcceil(substr($n, 1)) : strtok($n, '.');
}

function bcround($n, $p = 0)
{
    $e = bcpow(10, $p + 1);
    return bcdiv(bcadd(bcmul($n, $e, 0), bcnegative($n) ? -5 : 5), $e, $p);
}
function bcnegative($n)
{
    return strpos($n, '-') === 0; // Is the number less than 0?
}

function bcceil($n)
{
    return bcnegative($n) ? (($v = bcfloor(substr($n, 1))) ? "-$v" : $v)
                          : bcadd(strtok($n, '.'), strtok('.') != 0);
}

function bcfloor($n)
{
    return bcnegative($n) ? '-' . bcceil(substr($n, 1)) : strtok($n, '.');
}

function bcround($n, $p = 0)
{
    $e = bcpow(10, $p + 1);
    return bcdiv(bcadd(bcmul($n, $e, 0), bcnegative($n) ? -5 : 5), $e, $p);
}
仄言 2024-08-16 05:18:07

以下是支持负数和舍入精度参数的参数。

function bcceil($val) {
    if (($pos = strpos($val, '.')) !== false) {
        if ($val[$pos+1] != 0 && $val[0] != '-')
            return bcadd(substr($val, 0, $pos), 1, 0);
        else
            return substr($val, 0, $pos);
    }
    return $val;
}

function bcfloor($val) {
    if (($pos = strpos($val, '.')) !== false) {
        if ($val[$pos+1] != 0 && $val[0] == '-')
            return bcsub(substr($val, 0, $pos), 1, 0);
        else
            return substr($val, 0, $pos);
    }
    return $val;
}

function bcround($val, $precision = 0) {
    if (($pos = strpos($val, '.')) !== false) {
        if ($precision > 0) {
            $int = substr($val, 0, $pos);
            $pos2 = ++$pos+$precision;
            if ($pos2 < strlen($val)) {
                $val2 = sprintf('%s.%s', substr($val, $pos, $pos2-$pos), substr($val, $pos2));
                $val2 = $val2[0] >= 5 ? bcceil($val2) : bcfloor($val2);
                if (strlen($val2) > $precision)
                    return bcadd($int, $val[0] == '-' ? -1 : 1, 0);
                else
                    return sprintf('%s.%s', $int, rtrim($val2, '0'));
            }
            return $val;
        } else {
            if ($val[$pos+1] >= 5)
                return ($val[0] == '-' ? bcfloor($val) : bcceil($val));
            else
                return ($val[0] == '-' ? bcceil($val) : bcfloor($val));
        }
    }
    return $val;
}

Here's ones that support negative numbers and precision argument for rounding.

function bcceil($val) {
    if (($pos = strpos($val, '.')) !== false) {
        if ($val[$pos+1] != 0 && $val[0] != '-')
            return bcadd(substr($val, 0, $pos), 1, 0);
        else
            return substr($val, 0, $pos);
    }
    return $val;
}

function bcfloor($val) {
    if (($pos = strpos($val, '.')) !== false) {
        if ($val[$pos+1] != 0 && $val[0] == '-')
            return bcsub(substr($val, 0, $pos), 1, 0);
        else
            return substr($val, 0, $pos);
    }
    return $val;
}

function bcround($val, $precision = 0) {
    if (($pos = strpos($val, '.')) !== false) {
        if ($precision > 0) {
            $int = substr($val, 0, $pos);
            $pos2 = ++$pos+$precision;
            if ($pos2 < strlen($val)) {
                $val2 = sprintf('%s.%s', substr($val, $pos, $pos2-$pos), substr($val, $pos2));
                $val2 = $val2[0] >= 5 ? bcceil($val2) : bcfloor($val2);
                if (strlen($val2) > $precision)
                    return bcadd($int, $val[0] == '-' ? -1 : 1, 0);
                else
                    return sprintf('%s.%s', $int, rtrim($val2, '0'));
            }
            return $val;
        } else {
            if ($val[$pos+1] >= 5)
                return ($val[0] == '-' ? bcfloor($val) : bcceil($val));
            else
                return ($val[0] == '-' ? bcceil($val) : bcfloor($val));
        }
    }
    return $val;
}
青巷忧颜 2024-08-16 05:18:07

我选择 Alix Axel 的变体进行舍入,因为它是最快的,因为它只使用加法和减法,而不使用乘法和除法。为了在开始时以负精度舍入,我使用了标准函数:

sprintf('%.0F', round($result, $operand_value))

但我遇到了描述的问题 此处。因此,我扩展了这个变体以实现负精度:

function bcround($number, $precision)
{
    if($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;
    }
    else
    {
        $mod = bcmod($number, bcpow(10, -$precision));
        $sub = bcsub($number, $mod);
        if($mod[0] != '-')
        {
            $add = $mod[0] >= 5 ? bcpow(10, strlen($mod)) : 0;
        }
        else
        {
            $add = $mod[1] >= 5 ? '-'.bcpow(10, strlen($mod)-1) : 0;
        }
        return bcadd($sub, $add);
    }
}

通过递归的更优雅和更短的选项:

function bcround($number, $precision)
{
    if($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;
    }
    else
    {       
        $pow = bcpow(10, -$precision);
        return bcmul(bcround(bcdiv($number, $pow, -$precision), 0), $pow);
    }
}

但它速度较慢,因为它使用两个操作(除法和乘法),而不是在第一种情况下查找除法余数(mod)的一个操作。速度测试已经证实了这一点:

第一个变体。总迭代次数:10000。持续时间:0.24502515792847 秒。

第二个变体。总迭代次数:10000。持续时间:0.35303497314453 秒。

I chose the Alix Axel's variant for rounding as it is the fastest since it only uses addition and subtraction, not multiplication and division. To round with negative precision at the beginnig I used standard function:

sprintf('%.0F', round($result, $operand_value))

But I faced the problem described here. So I extended this variant for negative precision:

function bcround($number, $precision)
{
    if($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;
    }
    else
    {
        $mod = bcmod($number, bcpow(10, -$precision));
        $sub = bcsub($number, $mod);
        if($mod[0] != '-')
        {
            $add = $mod[0] >= 5 ? bcpow(10, strlen($mod)) : 0;
        }
        else
        {
            $add = $mod[1] >= 5 ? '-'.bcpow(10, strlen($mod)-1) : 0;
        }
        return bcadd($sub, $add);
    }
}

A more elegant and shorter option through recursion:

function bcround($number, $precision)
{
    if($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;
    }
    else
    {       
        $pow = bcpow(10, -$precision);
        return bcmul(bcround(bcdiv($number, $pow, -$precision), 0), $pow);
    }
}

But it is slower because it uses two operations (division and multiplication) versus one operation of finding the remainder of the division (mod) in the first case. Speed tests have confirmed this:

First variant. Total iterations:10000. Duration: 0.24502515792847 seconds.

Second variant. Total iterations:10000. Duration: 0.35303497314453 seconds.

萧瑟寒风 2024-08-16 05:18:07

仅使用 bcmath 函数来执行此操作:

function bcceil($number, $precision = 0) {
    $delta = bcdiv('9', bcpow(10, $precision + 1), $precision + 1);
    $number = bcadd($number, $delta, $precision + 1);
    $number = bcadd($number, '0', $precision);
    return $number;
}

function bcfloor($number, $precision = 0) {
    $number = bcadd($number, '0', $precision);
    return $number;
}

对于测试:

$numbers = [
    '1', '1.1', '1.4', '1.5', '1.9',
    '1.01', '1.09', '1.10', '1.19', '1.90', '1.99',
    '2'
];

foreach ($numbers as $n) {
    printf("%s (ceil)--> %s\n", $n, bcceil($n, 1));
}

printf("\n");

foreach ($numbers as $n) {
    printf("%s (floor)--> %s\n", $n, bcfloor($n, 1));
}

以及测试结果:

1 (ceil)--> 1.0
1.1 (ceil)--> 1.1
1.4 (ceil)--> 1.4
1.5 (ceil)--> 1.5
1.9 (ceil)--> 1.9
1.01 (ceil)--> 1.1
1.09 (ceil)--> 1.1
1.10 (ceil)--> 1.1
1.19 (ceil)--> 1.2
1.90 (ceil)--> 1.9
1.99 (ceil)--> 2.0
2 (ceil)--> 2.0

1 (floor)--> 1.0
1.1 (floor)--> 1.1
1.4 (floor)--> 1.4
1.5 (floor)--> 1.5
1.9 (floor)--> 1.9
1.01 (floor)--> 1.0
1.09 (floor)--> 1.0
1.10 (floor)--> 1.1
1.19 (floor)--> 1.1
1.90 (floor)--> 1.9
1.99 (floor)--> 1.9
2 (floor)--> 2.0

Only use bcmath functions to do that:

function bcceil($number, $precision = 0) {
    $delta = bcdiv('9', bcpow(10, $precision + 1), $precision + 1);
    $number = bcadd($number, $delta, $precision + 1);
    $number = bcadd($number, '0', $precision);
    return $number;
}

function bcfloor($number, $precision = 0) {
    $number = bcadd($number, '0', $precision);
    return $number;
}

For test:

$numbers = [
    '1', '1.1', '1.4', '1.5', '1.9',
    '1.01', '1.09', '1.10', '1.19', '1.90', '1.99',
    '2'
];

foreach ($numbers as $n) {
    printf("%s (ceil)--> %s\n", $n, bcceil($n, 1));
}

printf("\n");

foreach ($numbers as $n) {
    printf("%s (floor)--> %s\n", $n, bcfloor($n, 1));
}

And the test results:

1 (ceil)--> 1.0
1.1 (ceil)--> 1.1
1.4 (ceil)--> 1.4
1.5 (ceil)--> 1.5
1.9 (ceil)--> 1.9
1.01 (ceil)--> 1.1
1.09 (ceil)--> 1.1
1.10 (ceil)--> 1.1
1.19 (ceil)--> 1.2
1.90 (ceil)--> 1.9
1.99 (ceil)--> 2.0
2 (ceil)--> 2.0

1 (floor)--> 1.0
1.1 (floor)--> 1.1
1.4 (floor)--> 1.4
1.5 (floor)--> 1.5
1.9 (floor)--> 1.9
1.01 (floor)--> 1.0
1.09 (floor)--> 1.0
1.10 (floor)--> 1.1
1.19 (floor)--> 1.1
1.90 (floor)--> 1.9
1.99 (floor)--> 1.9
2 (floor)--> 2.0
梦明 2024-08-16 05:18:07
function getBcRound($number, $precision = 0)
{
    $precision = ($precision < 0)
               ? 0
               : (int) $precision;
    if (strcmp(bcadd($number, '0', $precision), bcadd($number, '0', $precision+1)) == 0) {
        return bcadd($number, '0', $precision);
    }
    if (getBcPresion($number) - $precision > 1) {
        $number = getBcRound($number, $precision + 1);
    }
    $t = '0.' . str_repeat('0', $precision) . '5';
    return $number < 0
           ? bcsub($number, $t, $precision)
           : bcadd($number, $t, $precision);
}

function getBcPresion($number) {
    $dotPosition = strpos($number, '.');
    if ($dotPosition === false) {
        return 0;
    }
    return strlen($number) - strpos($number, '.') - 1;
}

var_dump(getBcRound('3', 0) == number_format('3', 0));
var_dump(getBcRound('3.4', 0) == number_format('3.4', 0));
var_dump(getBcRound('3.56', 0) == number_format('3.6', 0));
var_dump(getBcRound('1.95583', 2) == number_format('1.95583', 2));
var_dump(getBcRound('5.045', 2) == number_format('5.045', 2));
var_dump(getBcRound('5.055', 2) == number_format('5.055', 2));
var_dump(getBcRound('9.999', 2) == number_format('9.999', 2));
var_dump(getBcRound('5.0445', 5) == number_format('5.044500', 5));
var_dump(getBcRound('5.0445', 4) == number_format('5.04450', 4));
var_dump(getBcRound('5.0445', 3) == number_format('5.0445', 3));
var_dump(getBcRound('5.0445', 2) == number_format('5.045', 2));
var_dump(getBcRound('5.0445', 1) == number_format('5.05', 1));
var_dump(getBcRound('5.0445', 0) == number_format('5.0', 0));//
var_dump(getBcRound('5.04455', 2) == number_format('5.045', 2));
var_dump(getBcRound('99.999', 2) == number_format('100.000', 2));
var_dump(getBcRound('99.999') == number_format('99.999', 0));
var_dump(getBcRound('99.999', 'a') == number_format('99.999', 0));
var_dump(getBcRound('99.999', -1.5) == number_format('99.999', 0));
var_dump(getBcRound('-0.00001', 2) == number_format('-0.000', 2));
var_dump(getBcRound('-0.0000', 2) == number_format('0', 2));
var_dump(getBcRound('-4.44455', 2) == number_format('-4.445', 2));
var_dump(getBcRound('-4.44555', 0) == number_format('-4.5', 0));
var_dump(getBcRound('-4.444444444444444444444444444444444444444444445', 0) == number_format('-4.5', 0));
function getBcRound($number, $precision = 0)
{
    $precision = ($precision < 0)
               ? 0
               : (int) $precision;
    if (strcmp(bcadd($number, '0', $precision), bcadd($number, '0', $precision+1)) == 0) {
        return bcadd($number, '0', $precision);
    }
    if (getBcPresion($number) - $precision > 1) {
        $number = getBcRound($number, $precision + 1);
    }
    $t = '0.' . str_repeat('0', $precision) . '5';
    return $number < 0
           ? bcsub($number, $t, $precision)
           : bcadd($number, $t, $precision);
}

function getBcPresion($number) {
    $dotPosition = strpos($number, '.');
    if ($dotPosition === false) {
        return 0;
    }
    return strlen($number) - strpos($number, '.') - 1;
}

var_dump(getBcRound('3', 0) == number_format('3', 0));
var_dump(getBcRound('3.4', 0) == number_format('3.4', 0));
var_dump(getBcRound('3.56', 0) == number_format('3.6', 0));
var_dump(getBcRound('1.95583', 2) == number_format('1.95583', 2));
var_dump(getBcRound('5.045', 2) == number_format('5.045', 2));
var_dump(getBcRound('5.055', 2) == number_format('5.055', 2));
var_dump(getBcRound('9.999', 2) == number_format('9.999', 2));
var_dump(getBcRound('5.0445', 5) == number_format('5.044500', 5));
var_dump(getBcRound('5.0445', 4) == number_format('5.04450', 4));
var_dump(getBcRound('5.0445', 3) == number_format('5.0445', 3));
var_dump(getBcRound('5.0445', 2) == number_format('5.045', 2));
var_dump(getBcRound('5.0445', 1) == number_format('5.05', 1));
var_dump(getBcRound('5.0445', 0) == number_format('5.0', 0));//
var_dump(getBcRound('5.04455', 2) == number_format('5.045', 2));
var_dump(getBcRound('99.999', 2) == number_format('100.000', 2));
var_dump(getBcRound('99.999') == number_format('99.999', 0));
var_dump(getBcRound('99.999', 'a') == number_format('99.999', 0));
var_dump(getBcRound('99.999', -1.5) == number_format('99.999', 0));
var_dump(getBcRound('-0.00001', 2) == number_format('-0.000', 2));
var_dump(getBcRound('-0.0000', 2) == number_format('0', 2));
var_dump(getBcRound('-4.44455', 2) == number_format('-4.445', 2));
var_dump(getBcRound('-4.44555', 0) == number_format('-4.5', 0));
var_dump(getBcRound('-4.444444444444444444444444444444444444444444445', 0) == number_format('-4.5', 0));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文