Php 如何对bcmath数字进行分层、分层和四舍五入?

Php 如何对bcmath数字进行分层、分层和四舍五入?,php,rounding,floor,bcmath,ceil,Php,Rounding,Floor,Bcmath,Ceil,我需要模拟的确切功能,以及bcmath numbers上的函数,但不幸的是,由于不支持负数,且round()函数的精度参数丢失 我想知道是否有人能想出一个简单而优雅的解决方案来解决这个问题 非常感谢您的所有意见,谢谢以下是一些支持负数和精度参数进行舍入的函数 function bcceil($val) { if (($pos = strpos($val, '.')) !== false) { if ($val[$pos+1] != 0 && $val[0]

我需要模拟的确切功能,以及bcmath numbers上的函数,但不幸的是,由于不支持负数,且round()函数的精度参数丢失

我想知道是否有人能想出一个简单而优雅的解决方案来解决这个问题


非常感谢您的所有意见,谢谢

以下是一些支持负数和精度参数进行舍入的函数

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;
}
函数bccell($val){
如果($pos=strpos($val,'.')!==false){
如果($val[$pos+1]!=0&$val[0]!='-'))
返回bcadd(substr($val,0,$pos),1,0);
其他的
返回substr($val,0,$pos);
}
返回$val;
}
功能楼层($val){
如果($pos=strpos($val,'.')!==false){
如果($val[$pos+1]!=0&&$val[0]='-'))
返回bcsub(substr($val,0,$pos),1,0);
其他的
返回substr($val,0,$pos);
}
返回$val;
}
函数bcround($val,$precision=0){
如果($pos=strpos($val,'.')!==false){
如果($precision>0){
$int=substr($val,0,$pos);
$pos2=++$pos+$精度;
如果($pos2=5bcceli($val2):bcfloor($val2);
if(strlen($val2)>precision美元)
返回bcadd($int,$val[0]='-'?-1:1,0);
其他的
返回sprintf('%s.%s',$int,rtrim($val2,'0');
}
返回$val;
}否则{
如果($val[$pos+1]>=5)
返回($val[0]='-'?b楼层($val):b单元($val));
其他的
返回($val[0]='-'?b单元($val):b楼层($val));
}
}
返回$val;
}

经过一夜的迷路,我想我已经找到了一个相当简单的解决方案,它是:

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
函数getBcRound($number,$precision=0) { $precision=($precision<0) ? 0 :(int)$精度; if(strcmp(bcadd($number,'0',$precision),bcadd($number,'0',$precision+1))==0){ 返回bcadd($number,$0',$precision); } if(getBcPresion($number)-$precision>1){ $number=getBcRound($number,$precision+1); } $t='0.'.str_repeat('0',$precision)。'5'; 返回$number<0 ?bcsub($number,$t,$precision) :bcadd($number,$t,$precision); } 函数getBcPresion($number){ $dotPosition=strpos($number,'.'); 如果($dotPosition==false){ 返回0; } 返回strlen($number)-strpos($number,'.')-1; } 变量转储(getBcRound('3',0)=数字格式('3',0)); 变量转储(getBcRound('3.4',0)=数字格式('3.4',0)); 变量转储(getBcRound('3.56',0)=数字格式('3.6',0)); 变量转储(getBcRound('1.95583',2)=数字格式('1.95583',2)); 变量转储(getBcRound('5.045',2)=数字格式('5.045',2)); 变量转储(getBcRound('5.055',2)=数字格式('5.055',2)); 变量转储(getBcRound('9.999',2)=数字格式('9.999',2)); 变量转储(getBcRound('5.0445',5)=数字格式('5.044500',5)); 变量转储(getBcRound('5.0445',4)=数字格式('5.04450',4)); 变量转储(getBcRound('5.0445',3)=数字格式('5.0445',3)); 变量转储(getBcRound('5.0445',2)=数字格式('5.045',2)); 变量转储(getBcRound('5.0445',1)=数字转储格式('5.05',1)); 变量转储(getBcRound('5.0445',0)=数字格式('5.0',0))// 变量转储(getBcRound('5.04455',2)=数字转储格式('5.045',2)); 变量转储(getBcRound('99.999',2)=数字转储格式('100.000',2)); 变量转储(getBcRound('99.999')==数字格式('99.999',0)); 变量转储(getBcRound('99.999','a')==number_格式('99.999',0)); 变量转储(getBcRound('99.999',-1.5)=数字转储格式('99.999',0)); 变量转储(getBcRound('-0.00001',2)=数字转储格式('-0.000',2)); 变量转储(getBcRound('-0.0000',2)=数字转储格式('0',2)); 变量转储(getBcRound('-4.4455',2)=数字转储格式('-4.445',2)); 变量转储(getBcRound('-4.44555',0)=数字转储格式('-4.5',0)); 变量转储(getBcRound('-4.444444445',0)=数字转储格式('-4.5',0));
仅使用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

我选择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.2450251792847秒


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

我还没有测试它,但我相信bcround(99.999,2)错误地返回99.100,没有?没有:$php-r'包括“bc.php”;变量转储(bcround(99.999,2));'字符串(3)“100”如果(strlen($val2)>$precision)”部分用于防止出现这种情况:请注意,“reko_t”的答案不正确。如果您想正确地取整,请转到“”并查看mwgamera的帖子。此函数为我生成完全不稳定和不正确的结果。例如:bcround('323.346',2)产生'323.34';bcround('323.006',2)产生'323'---我是不是遗漏了什么?我假设这应该是“一半向上”舍入?不管怎样,这都是错误的,因为没有可预测的模式
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);
    }
}