PHP B C Математическая библиотека игнорирует правила округления - PullRequest
0 голосов
/ 18 марта 2020

Я использую функцию bcdiv из PHP для вычисления некоторых вещей, но результат отличается от того, который должен быть. Вот пример кода:

$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"

В моем приложении много вычислений с плавающей запятой, и теперь я не уверен, как правильно их обрабатывать, нормальные математические вычисления имеют проблемы с плавающей запятой, но из b c математические не лучшая вещь, которую я должен использовать.

Итак, вот мои вопросы:

  1. Как я могу обрабатывать вычисления с плавающей запятой, учитывая, что результаты BCMath неверны, когда вы думаете о правилах округления в обычной математике?
  2. Как Вы (другие PHP программисты) вычисляете числа с плавающей точкой? Преобразовать их в целые числа невозможно в моем приложении.
  3. Что вы думаете о php -десятичной ?

Ответы [ 3 ]

1 голос
/ 22 марта 2020

bcdiv

bcdiv ( string $dividend , string $divisor [, int $scale = 0 ] ) : string

Параметры

  • dividend
    Дивиденд в виде строки.
  • divisor
    Делитель в виде строки.
  • scale
    Этот необязательный параметр используется для установки количества цифр после десятичного разряда в результате. Если опущено, по умолчанию используется глобальная шкала, установленная с помощью функции bcscale(), или откат до 0, если она не была установлена.

Как вы можете см., bcdiv 3-й параметр не для округления , но для масштаба , что означает, что он просто сохраняет это количество цифр.

Есть хорошая Q / A от Alix Axel по этой специфической c проблеме, которую вы можете увидеть здесь "Как потолочные, напольные и круглые числа 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.00

var_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)
0 голосов
/ 26 марта 2020

Спасибо, Христос Литрас, за то, что указал, что я сделал не так. Поскольку я использую вычисления 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

0 голосов
/ 20 марта 2020

Как я могу обрабатывать вычисления с плавающей запятой, учитывая, что у математики b c есть проблемы с округлением?

В последнее время наша компания написала программу расчетов для компании, занимающейся совместным использованием автомобилей. У нас была та же проблема, и мы использовали number_format ()

https://www.php.net/manual/de/function.number-format.php

Вы можете выбрать десятичные дроби, с которыми вы работаете, и они округляются правильно :

0.1-0.4 => 0 0.5-0.9 => 1

Мы рассчитали цены в обычном порядке и в итоге использовали функцию для форматирования вывода.

Надеюсь помогает.

...