Я хочу сравнить два поплавка на PHP, как в этом примере кода:
$a = 0.17; $b = 1 - 0.83; //0.17 if($a == $b ){ echo 'a and b are same'; } else { echo 'a and b are not same'; }
В этом коде он возвращает результат условия else
вместо условия if
, хотя $a
и $b
одинаковы. Есть ли специальный способ обработки / сравнения float в PHP?
Если да, то, пожалуйста, помогите мне решить эту проблему.
Или есть проблема с моей конфигурацией сервера?
Если вы сделаете это так, они должны быть одинаковыми. Но обратите внимание, что характеристикой значений с плавающей запятой является то, что вычисления, которые, как представляется, приводят к одному и тому же значению, не обязательно должны быть идентичными. Так что, если $a
– буквальный .17
и $b
прибывает туда через расчет, вполне возможно, что они разные, хотя оба показывают одинаковое значение.
Обычно вы никогда не сравниваете значения с плавающей запятой для равенства, как это, вам нужно использовать наименьшую приемлемую разницу:
if (abs(($a-$b)/$b) < 0.00001) { echo "same"; }
Что-то вроде того.
Сначала прочитайте красное предупреждение http://www.php.net/manual/en/language.types.float.php . Вы никогда не должны сравнивать float для равенства. Вы должны использовать технику epsilon.
Например:
if (abs($a-$b) < EPSILON) { … }
где EPSILON
постоянно представляет собой очень небольшое число (вы должны определить его)
Или попробуйте использовать математические функции bc:
<?php $a = 0.17; $b = 1 - 0.83; //0.17 echo "$a == $b (core comp oper): ", var_dump($a==$b); echo "$a == $b (with bc func) : ", var_dump( bccomp($a, $b)==0 );
Результат:
0.17 == 0.17 (core comp oper): bool(false) 0.17 == 0.17 (with bc func) : bool(true)
Как говорилось ранее, будьте очень осторожны при выполнении сравнений с плавающей запятой (будь то равно, больше или меньше) в PHP. Однако, если вас интересует только несколько значащих цифр, вы можете сделать что-то вроде:
$a = round(0.17, 2); $b = round(1 - 0.83, 2); //0.17 if($a == $b ){ echo 'a and b are same'; } else { echo 'a and b are not same'; }
Использование округления до 2 знаков после запятой (или 3 или 4) приведет к ожидаемому результату.
Если у вас есть значения с плавающей запятой для сравнения с равенством, простой способ избежать риска стратегии внутреннего округления ОС, языка, процессора или так далее – сравнить строковое представление значений, например:
if (''. $ a === ''. $ b) {…}
Таким образом, вы можете увидеть равенство, и ответ PHP будет равен вашим пожеланиям. Если он равен вам, когда вы читаете значения , тогда утверждение будет истинным, как и ожидалось
Вот решение для сравнения плавающих точек или десятичных чисел
//$fd['someVal'] = 2.9; //$i for loop variable steps 0.1 if((string)$fd['someVal']== (string)$i) { //Same }
Вставьте decimal
переменную в string
и все будет в порядке.
Если вы напишете это так, то это, вероятно, будет работать, поэтому, я думаю, вы упростили это для вопроса. (И держать вопрос простым и лаконичным, как правило, очень хорошо.)
Но в этом случае я считаю, что один результат – это вычисление, а один результат – константа.
Это нарушает кардинальное правило программирования с плавающей запятой: никогда не делайте сравнения равенства.
Причины этого немного тонкие 1, но важно помнить, что они обычно не работают (за исключением, как это ни парадоксально, для интегральных значений), и что альтернатива – это нечеткое сравнение по строкам:
if abs(a - y) < epsilon
1. Одна из основных проблем связана с тем, как мы пишем цифры в программах. Мы пишем их как десятичные строки, и в результате большинство записываемых нами дробей не имеют точных представлений машин. У них нет точных конечных форм, потому что они повторяются в двоичном виде. Каждая машинная дробь является рациональным числом вида x / 2 n . Теперь константы десятичные, а каждая десятичная константа – рациональное число вида x / (2 n * 5 m ). Номера 5 м нечетны, поэтому для любого из них нет 2 n фактора. Только тогда, когда m == 0 есть конечное представление как в двоичном, так и в десятичном разложении дроби. Итак, 1.25 точна, потому что это 5 / (2 2 * 5 0 ), но 0,1 не потому, что это 1 / (2 0 * 5 1 ). Фактически, в серии 1.01 .. 1.99 только 3 из чисел точно представлены: 1,25, 1,50 и 1,75.
Это работает для меня на PHP 5.3.27.
$payments_total = 123.45; $order_total = 123.45; if (round($payments_total, 2) != round($order_total, 2)) { // they don't match }
Если у вас есть небольшое конечное число десятичных точек, которое будет приемлемым, то следующее будет хорошо работать (хотя и с более низкой производительностью, чем решение epsilon):
$a = 0.17; $b = 1 - 0.83; //0.17 if (number_format($a, 3) == number_format($b, 3)) { echo 'a and b are same'; } else { echo 'a and b are not same'; }
Сравнение поплавков для равенства имеет наивный O (n) алгоритм.
Вы должны преобразовать каждое значение float в строку, а затем сравнить каждую цифру, начиная с левой стороны строкового представления каждого float, используя операции сравнения целого числа. PHP будет автоматически обновлять цифру в каждой позиции индекса до целого числа перед сравнением. Первая цифра больше, чем другая, приведет к разрыву цикла и объявит поплавок, к которому он принадлежит, как к большему из двух. В среднем будет 1/2 * n сравнений. Для поплавков, равных друг другу, будет проведено n сравнений. Это наихудший сценарий для алгоритма. Наилучший вариант сценария состоит в том, что первая цифра каждого поплавка отличается, что приводит только к одному сравнению.
Вы не можете использовать INTEGER COMPARISON OPERATORS на значениях raw float с целью получения полезных результатов. Результаты таких операций не имеют смысла, потому что вы не сравниваете целые числа. Вы нарушаете домен каждого оператора, который генерирует бессмысленные результаты. Это справедливо и для дельта-сравнения.
Используйте целые операторы сравнения для того, для чего они предназначены: сравнение целых чисел.
УПРОЩЕННОЕ РЕШЕНИЕ:
<?php function getRand(){ return ( ((float)mt_rand()) / ((float) mt_getrandmax()) ); } $a = 10.0 * getRand(); $b = 10.0 * getRand(); settype($a,'string'); settype($b,'string'); for($idx = 0;$idx<strlen($a);$idx++){ if($a[$idx] > $b[$idx]){ echo "{$a} is greater than {$b}.<br>"; break; } else{ echo "{$b} is greater than {$a}.<br>"; break; } } ?>
Было бы лучше использовать собственное сравнение PHP :
bccomp($a, $b, 3) // Third parameter - the optional scale parameter // is used to set the number of digits after the decimal place // which will be used in the comparison.
Возвращает 0, если оба операнда равны, 1, если left_operand больше, чем right_operand, -1 в противном случае.
Я ненавижу это говорить, но «работает для меня»:
Beech:~ adamw$ php -v PHP 5.3.1 (cli) (built: Feb 11 2010 02:32:22) Copyright (c) 1997-2009 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies Beech:~ adamw$ php -f test.php a and b are same
Теперь сравнения с плавающей запятой в целом сложны – вещи, которые вы, возможно, ожидаете, будут одинаковыми, не являются (из-за ошибок округления и / или нюансов представления). Вы можете прочитать http://floating-point-gui.de/
if( 0.1 + 0.2 == 0.3 ){ echo 'a and b are same'; } else { echo 'a and b are not same'; }
Это вызовет проблемы из-за стандартной арифметики с плавающей точкой IEEE (которая имеет эту проблему).
Одна забытая ловушка здесь …
Если вы работаете с подписанными поплавками, вам нужно сделать два сравнения, чтобы проверить близость:
$a - $b < EPSILON && $b - $a < EPSILON
//You can compare if less or more. $parcela='250.23'; //total value $tax = (double) '15.23'; //tax value $taxaPercent=round((100*$tax)/$parcela,2); //tax percent $min=(double) '2.50';// minimum tax percent if($taxaPercent < $min ){ // tax error tax is less than 2.5 }
function floatcmp($f1,$f2,$precision = 10) { $e = pow(10,$precision); return (intval($f1 * $e) == intval($f2 * $e)); }
Прецедент
$a = 0.17; $b = 0.17; echo floatcmp($a,$b) ? 'yes' : 'no'; // yes echo floatcmp($a,$b + 0.01) ? 'yes' : 'no'; // no
function compareFloats($a, $b){ list($a_int, $a_dec) = explode('.', strval($a)); list($b_int, $b_dec) = explode('.', strval($b)); if(intval($a_int) == intval($b_int) && intval($a_dec) == intval($b_dec)){ return 'same'; }else{ if((intval($a_int) < intval($b_int)) || (intval($a_int) === intval($b_int) && intval($a_dec) < intval($b_dec))){ return 'smaller'; } if((intval($a_int) > intval($b_int)) || (intval($a_int) === intval($b_int) && intval($a_dec) > intval($b_dec))){ return 'bigger'; } return 'error'; } }