Я нашел некоторое решение для проблемы с плавающей запятой в 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 соответствует лучшим двум цифрам, расчету денег
@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 – лучшее решение. Но все же я не уверен, что обходной путь не удастся или не для подделки и добавления
Арифметика с плавающей точкой считается многими эзотерическими субъектами. Это довольно удивительно, потому что плавающая точка вездесуща в компьютерных системах. Большинство дробных чисел не имеют точного представления как двоичной дроби, поэтому происходит некоторое округление. Хорошим началом является то, что каждый компьютерный ученый должен знать о арифметике с плавающей точкой
Могу ли я полагаться на это решение, если мне нужны только точные 2-разрядные вычисления (деньги)?
Если вам нужны точные 2 цифры, тогда ответ НЕТ, вы не можете использовать настройки точности php, чтобы установить двузначное десятичное число раз, даже если вы not going to work on numbers higher than 10^6
.
Во время расчетов существует вероятность того, что точность длины может быть увеличена, если длина меньше 8
Если вы не можете дать мне ясный пример, когда эти решения не удались?
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
Какое значение php.ini.precision соответствует лучшим двум цифрам, расчету денег?
Расчет точности и денег – это две разные вещи … не рекомендуется использовать точность 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)
Что иллюстрирует это:
Я полагаю, что если вы просто обойдете свой результат, который придумаете, то это позаботится о вашей проблеме с плавающей запятой для вас, не прибегая к изменению конфигурации сервера в вашей конфигурации.
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 использует высокую точность вычислений и только затем сохраняет результат в более низкой точности (и вы не используете этот номер для продолжения и выполнения другие вычисления), но в целом это очень плохая идея, поскольку (по крайней мере) последняя цифра в вашем расчете полностью ненадежна.