猿问

PHP BC 数学库忽略舍入规则

我正在使用bcdivPHP 中的函数来计算一些东西,但结果与应有的不同。这是示例代码:


$val1 = 599.60;

$val2 = 60;


var_dump(bcdiv($val1, $val2, 0));

// result string(1) "9"

// should be "10"


var_dump(bcdiv($val1, $val2, 2));

// result string(4) "9.99"

// result ok, but


var_dump(bcdiv($val1, $val2, 1));

// result string(4) "9.9"

// should be "10" too

第一次的结果var_dump对我来说很奇怪,因为它应该是10而不是9。


其他BCMath函数 的结果相同:


$val1 = 599.99;

$val2 = 1;


var_dump(bcmul($val1, $val2, 0));

// result string(3) "599"

// should be "600"


var_dump(bcadd($val1, $val2, 0));

// result string(3) "600"

// should be "601"


var_dump(bcsub($val1, $val2, 0));

// result string(3) "598"

// should be "599"

我的应用程序中有很多浮点计算,现在我不知道如何正确处理它们,正常的数学计算有浮点问题,但从bc 数学计算不是我应该使用的最好的东西。

所以,这是我的问题:

  1. 当您考虑常规数学舍入规则时,考虑到 BCMath 结果是错误的,我该如何处理浮点计算?

  2. 您(其他 PHP 程序员)如何计算浮点数?在我的应用程序中无法将它们转换为整数。

  3. 你觉得php-decimal怎么样?


隔江千里
浏览 112回答 2
2回答

慕的地8271018

bcdivbcdiv ( string $dividend , string $divisor [, int $scale = 0 ] ) : string参数dividend股息,作为一个字符串。divisor除数,作为字符串。scale此可选参数用于设置结果中小数点后的位数。如果省略,它将默认使用该bcscale()函数全局设置的比例,如果尚未设置,则回退到 0。如您所见,bcdiv第三个参数不是用于 rounding,而是用于 scale,这意味着它只保留该位数。Alix Axel对这个特定问题有一个很好的 Q/A ,您可以在这里看到“如何计算上限、下限和四舍五入 bcmath 数字?” .在他的回答中,他有一个自定义函数bcround,可以按照您的预期进行舍入: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;}$val1 = 599.60;$val2 = 60;var_dump(bcround(bcdiv($val1, $val2, 10), 0));// string(2) "10"var_dump(bcround(bcdiv($val1, $val2, 10), 2));// string(4) "9.99"var_dump(bcround(bcdiv($val1, $val2, 10), 1));// string(4) "10.0"处理货币数字的正确方法我不确定您的数字是否指的是价格和货币,但如果是这种情况,那么处理货币计算的最佳方法是将所有值设为美分并使用整数进行数学运算。对于您的示例:$val1 = 59960; // 59960 cents == 599.60$val2 = 6000;  // 6000 cents == 60.00var_dump($val1 / $val2);// float(9.9933333333333)var_dump(round($val1 / $val2, 0));// float(10)var_dump(round($val1 / $val2, 2));// float(9.99)var_dump(round($val1 / $val2, 1));// float(10)

犯罪嫌疑人X

因为我在多个类中使用BCMath计算并且我没有足够的时间将所有带有浮点数的地方重写为整数,所以我决定创建简单的特征。它用四舍五入的值解决了我所有的问题。这是特征代码: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);    }}在这里您可以查看结果:http ://sandbox.onlinephpfunctions.com/code/5b602173a1825a2b2b9f167a63646477c5105a3c
随时随地看视频慕课网APP
我要回答