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

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

precision = 14 настройки 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
  • Мне не нужно сравнивать числа

ОБНОВИТЬ

@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 – лучшее решение. Но все же я не уверен, что обходной путь не удастся или не для подделки и добавления

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

Введение

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

Вопросов

Вопрос 1

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

Ответ 1

Если вам нужны точные 2 цифры, тогда ответ НЕТ, вы не можете использовать настройки точности php, чтобы установить двузначное десятичное число раз, даже если вы 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 это выглядит согласованным со мной.

Обновить

Вопрос 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

введите описание изображения здесь

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

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

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

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

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

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

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

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

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

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

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

прецизионное 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) 

Редактировать:

Я настаиваю: этот параметр не влияет на то, как 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); 

… печатает:

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

Что иллюстрирует это:

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

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

 round(342349.23 - 341765.07, 2) = 584.16 

Если вы используете точность = 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 использует высокую точность вычислений и только затем сохраняет результат в более низкой точности (и вы не используете этот номер для продолжения и выполнения другие вычисления), но в целом это очень плохая идея, поскольку (по крайней мере) последняя цифра в вашем расчете полностью ненадежна.