前言:以前写程序用户余额和价格数据,都是写的小数点后最多存储2位数字,这次不同 要精准到5位小数,从decimal(10,2) 干到decimal(10,5) 了,然后惯用的round和floatval函数也不好使了。
round($number, 5) 这样偶尔也不行,只能计算到后四位小数,于是我发现可以用bcmath高精度数学计算,适用于需要处理大数或高精度浮点数的场景。
bcmath 函数使用字符串来表示数字,因此可以避免浮点数精度问题,下方是实例演示:
//加法
$num1 = "1.234567890123456789";
$num2 = "9.876543210987654321";
$result = bcadd($num1, $num2, 10); // 结果保留10位小数
echo $result; // 输出: 11.1111111011
//减法
$num1 = "10.123456789";
$num2 = "5.987654321";
$result = bcsub($num1, $num2, 6); // 结果保留6位小数
echo $result; // 输出: 4.135802
//乘法
$num1 = "2.5";
$num2 = "3.5";
$result = bcmul($num1, $num2, 2); // 结果保留2位小数
echo $result; // 输出: 8.75
//除法
$num1 = "10";
$num2 = "3";
$result = bcdiv($num1, $num2, 8); // 结果保留8位小数
echo $result; // 输出: 3.33333333
//比较俩个数
$num1 = "1.23456789";
$num2 = "1.23456788";
$result = bccomp($num1, $num2, 8); // 比较到8位小数
echo $result; // 输出: 1 ($num1 > $num2)
//针对高精度四舍五入需求的 完美解决方案,提供一个可直接使用的 bcround 函数,支持任意小数位数且无精度损失
// 基本示例
echo bcround('1.234564', 5); // 输出: 1.23456
echo bcround('1.234565', 5); // 输出: 1.23457
echo bcround('-9.999999', 3); // 输出: -10.000 (进位导致整数部分变化)
// 边界测试
echo bcround('0.000004999', 5); // 输出: 0.00000
echo bcround('0.000005', 5); // 输出: 0.00001
echo bcround('123456789.123456789', 8); // 输出: 123456789.12345679
/**
* 高精度四舍五入函数(支持任意小数位数)
* @param string|int|float $number 输入数字(建议以字符串形式传入避免精度丢失)
* @param int $scale 保留的小数位数(必须 >= 0)
* @return string 四舍五入后的字符串结果
*/
function bcround($number, int $scale = 0): string
{ $number="$number";
if (!is_numeric($number)) {
throw new InvalidArgumentException("输入必须是数字");
}
if ($scale < 0) {
throw new InvalidArgumentException("小数位数不能为负数");
}
$number = (string)$number; // 强制转换为字符串处理
// 分离整数和小数部分
$parts = explode('.', $number);
$integerPart = $parts[0];
$decimalPart = isset($parts[1]) ? substr($parts[1], 0, $scale + 1) : ''; // 截取到需要检查的位数+1
// 若小数位数不足,直接补零返回
if (strlen($decimalPart) <= $scale) {
return $scale === 0 ? $integerPart : sprintf("%s.%s", $integerPart, str_pad($decimalPart, $scale, '0'));
}
// 获取需要四舍五入的位置
$roundDigit = (int)$decimalPart[$scale];
$decimalBase = substr($decimalPart, 0, $scale);
// 判断是否需要进位
if ($roundDigit < 5) {
return $scale === 0 ? $integerPart : sprintf("%s.%s", $integerPart, $decimalBase);
}
// 处理进位逻辑
$carry = '0.' . str_repeat('0', $scale) . '1';
return bcadd(
sprintf("%s.%s", $integerPart, $decimalBase),
$carry,
$scale
);
}
评论
发表评论: