Подтвердить что ты не робот

Могу ли я полагаться на обходное решение PHP php.ini для проблемы с плавающей точкой

Я нашел некоторое решение для проблема с плавающей запятой в PHP:

Настройка php.ini precision = 14

342349.23 - 341765.07 = 584.15999999992 // floating point problem

Настройка php.ini, скажем precision = 8

342349.23 - 341765.07 = 584.16 // voila!

Демо: http://codepad.org/r7o086sS

Насколько это плохо?

1. Могу ли я полагаться на это решение, если мне нужны только точные двухзначные вычисления (деньги)?

2. Если вы не можете дать мне ясный пример, когда эти решения не удались?

Изменить: 3. Какое значение php.ini.precision соответствует лучшим двум цифрам, расчету денег


  • Обратите внимание: я не могу использовать целочисленные вычисления (float * 100 = центы), это слишком поздно для этого.
  • Я не буду работать над цифрами выше 10 ^ 6
  • Мне не нужно сравнивать числа

UPDATE

@Baba ответ хороший, но он использовал precision=20, precision=6 в своих тестах... Итак, я не уверен, что это сработает или нет.

Обратите внимание на следующее:

Скажем precision = 8, и только то, что я делаю, это дополнение + и вычитание -

A + B = C

A - B = C

Вопрос 1: Будет ли прерывание с точностью до чисел между 0..999999.99, где A и B - число с десятичными знаками? Если да, пожалуйста, предоставьте мне пример.

Простой тест выполнит задание:

// if it fails what if I use 9,10,11 ???
// **how to find when it fails??? **
ini_set('precision', 8); 
for($a=0;$a<999999.99;$a+=0.01) {
  for($b=0;$b<999999.99;$b+=0.01) {
     // mind I don't need to test comparision (round($a-$b,2) == ($a-$b))
     echo ($a + $b).','.($a - $b)." vs ";
     echo round($a + $b, 2).','.round($a - $b, 2)."\n";
  }
}

но, очевидно, 99999999 * 2 слишком большая работа, поэтому я не могу запустить этот тест

Вопрос 2: Как оценивать/вычислять, когда обход ошибки точности не выполняется? Без таких сумасшедших тестов? Есть ли какой-либо математический *, прямой ответ для него? Как рассчитать будет неудачно или нет?

* Мне не нужно знать, как работают вычисления с плавающей запятой, но когда обходной путь выходит из строя, если вы знаете точность и диапазон A и B


Обратите внимание на Я действительно знаю, что центы и bcmath - лучшее решение. Но все же я не уверен, что обходной путь не удастся или не для подстановки и добавления

4b9b3361

Ответ 1

Введение

Арифметика с плавающей точкой рассматривается многими эзотерическими субъектами. Это довольно удивительно, потому что плавающая точка вездесуща в компьютерных системах. Большинство дробных чисел не имеют точного представления как двоичной дроби, поэтому происходит некоторое округление. Хорошее начало Что должен знать каждый компьютерный ученый о арифметике с плавающей точкой

Вопросы

Вопрос 1

Можно ли полагаться на это решение, если мне нужны только точные 2-разрядные вычисления (деньги)?

Ответ 1

Если вам нужна точные 2 цифры, тогда ответ НЕТ, вы не можете использовать настройки точности php, чтобы определить 2-значный десятичный все время, даже если вы not going to work on numbers higher than 10^6.

В ходе расчетов есть вероятность того, что точность длины может быть увеличена, если длина меньше 8

Вопрос 2

Если вы не можете дать мне ясный пример, когда эти решения не удались?

Ответ 2

ini_set('precision', 8); // your precision
$a =  5.88 ; // cost of 1kg
$q = 2.49 ;// User buys 2.49 kg
$b = $a * 0.01 ; // 10% Discount only on first kg ;
echo ($a * $q) - $b;

Выход

14.5824 <---- not precise 2 digits calculations even if precision is 8

Вопрос 3

Какое значение php.ini.precision соответствует лучшим двум цифрам, расчету денег?

Ответ 3

Расчет точности и денег - это две разные вещи... не рекомендуется использовать точность PHP в качестве базы для ваших финансовых расчетов или длины с плавающей запятой

Простой тест

Чтобы запустить пример с помощью bcmath, number_format и простого minus

Base

$a = 342349.23;
$b = 341765.07;

Example A

ini_set('precision', 20); // set to 20 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Выход

584.15999999997438863
584.15999999999996817    <----- Round having a party 
584.16
584.15  <-------- here is 15 because precision value is 20

Example B

ini_set('precision', 14); // change to  14 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Выход

584.15999999997
584.16
584.16
584.16  <-------- at 14 it changed to 16

Example C

ini_set('precision', 6); // change to  6 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Выход

584.16
584.16
584.16
584.00  <--- at 6 it changed to 00 

Example D

ini_set('precision', 3); // change to 3
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Выход

584
584
584.16   <-------------------------------- They only consistent value 
0.00  <--- at 3 .. everything is gone 

Заключение

Забудьте о плавающей запятой и просто вычислите в cents, а затем разделите на 100, если это слишком поздно, просто используйте number_format, он выглядит согласованно со мной.

Update

Вопрос 1: превалирует ли предел точности для чисел между 0..999999.99, где A и B - число с десятичными знаками? Если да, пожалуйста, предоставьте мне пример

Форма 0 до 999999.99 с шагом 0.01 составляет около 99,999,999 возможность комбинирования вашего цикла 9,999,999,800,000,000 Я действительно не думаю, что кто-то захочет запустить такой тест для вас.

Поскольку с плавающей запятой есть двоичные числа с конечной точностью, пытающиеся установить precision, будут иметь ограниченный эффект для обеспечения точности. Вот простой тест:

ini_set('precision', 8);

$a = 0.19;
$b = 0.16;
$c = 0.01;
$d = 0.01;
$e = 0.01;
$f = 0.01;
$g = 0.01;

$h = $a + $b + $c + $d + $e + $f + $g;

echo "Total: " , $h , PHP_EOL;


$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;

echo $i , PHP_EOL;

Выход

Total: 0.4
1.0408341E-17     <--- am sure you would expect 0.00 here ;

Попробуйте

echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;

Выход

0
0.00    <------ still confirms number_format is most accurate to maintain 2 digit 

Вопрос 2: Как оценить/рассчитать, когда обходное решение с ошибкой завершается неудачно? Без таких сумасшедших тестов? Есть ли математический *, прямой ответ для этого? Как вычислить будет неудачно или нет?

Остаток факта остается Плавающая точка Точность проблемы но для математических решений вы можете посмотреть

Мне не нужно знать, как работают вычисления с плавающей запятой, но когда обходной путь выходит из строя, если вы знаете точность и диапазон A и B

enter image description here

Не уверен, что это означает:)

Ответ 2

Я просто цитирую этот интересный сайт к проблеме. (Ожидания репутации не ожидаются:), но следует упомянуть:

Что я могу сделать, чтобы избежать этой проблемы (с плавающей запятой)?

Это зависит от того, какие вычисления вы делаете.

  • Если вам действительно нужны ваши результаты, чтобы точно добавить, особенно когда вы работаете с деньги: используйте специальный десятичный тип данных.

  • Если вы просто не хотите видеть все эти дополнительные десятичные знаки: просто отформатируйте результат, округленный до фиксированного числа десятичных знаков при его отображении.

  • Если у вас нет десятичного типа данных, альтернативой является работа с целыми числами, например. делать денежные расчеты целиком в центах. Но это больше работы и имеет некоторые недостатки.

На сайте также содержатся некоторые основные советы для PHP

Я бы использовал целые числа или создавал для него специальный тип Decimal.

Если вы решите использовать bcmath: Будьте осторожны, если вы передадите эти значения SQL-запросам или другим внешним программам. Это может привести к нежелательным побочным эффектам, если они не знают о точности. (Скорее всего)

Ответ 3

В соответствии с документами директива точности изменяет цифры, отображаемые при наборе чисел в строки:

точность integer
    Число значащих цифр, отображаемых в числах с плавающей запятой.

Итак, это в основном очень запутанная альтернатива number_format() или money_format(), за исключением того, что у него меньше вариантов форматирования, и он может пострадать от некоторых других побочных эффектов, о которых вы не знаете:

<?php

$_POST['amount'] = '1234567.89';

$amount = floatval($_POST['amount']);
var_dump($amount);

ini_set('precision', 5);
$amount = floatval($_POST['amount']);
var_dump($amount);

...

float(1234567.89)
float(1.2346E+6)

Edit:

Я настаиваю: этот параметр не влияет на то, как PHP делает математические вычисления с числами. Это просто волшебный способ изменить параметры формата при преобразовании из чисел с плавающей запятой (даже целых чисел!) В строки. Пример:

<?php

ini_set('precision', 2);

$amount = 1000;
$price = 98.76;
$total = $amount*$price;

var_dump($amount, $total);

ini_set('precision', 15);
var_dump($amount, $total);

... prints:

int(1000)
float(9.9E+4)
int(1000)
float(98760)

Что иллюстрирует, что:

  • Расчеты с плавающей запятой не затрагиваются, изменяется только отображение
  • Целые элементы не затрагиваются во всех случаях

Ответ 4

Я считаю, что если вы просто обойдете свой результат, который придумаете, то это позаботится о вашей проблеме с плавающей запятой для вас, не прибегая к изменениям на сервере в вашей конфигурации.

round(342349.23 - 341765.07, 2) = 584.16

Ответ 5

Если вы используете точность = 8, если вы используете 8-значное число, вы не можете быть уверены в 8-й цифре. Это может быть отключено на 1 от округления 9-й цифры.

Например,

12345678.1 -> 12345678
12345678.9 -> 12345679

Это может показаться не таким уж плохим, но рассмотрите

   (11111111.2 + 11111111.2) + 11111111.4
-> (11111111)                + 11111111.4
-> 22222222.4
-> 22222222

Если вы используете точность = 9, это будет 22222222.8, который будет округлен до 22222223.

Если вы просто делаете добавления и вычитания, вы должны использовать не менее 2 или более цифр точности, чем вам нужно избегать округления в этих вычислениях. Если вы делаете умножение или деление, вам может потребоваться больше. Использование минимального необходимого количества может привести к потере цифр здесь и там.

Итак, чтобы ответить на ваш вопрос, вы можете уйти от него, если вам повезет, а php использует высокую точность вычислений и только затем сохраняет результат с меньшей точностью (и вы не используете этот номер для перехода и другие вычисления), но в целом это очень плохая идея, поскольку (по крайней мере) последняя цифра в вашем расчете полностью ненадежна.