我想在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
相同。在PHP中有没有特殊的方法来处理/比较浮点数?
如果是,那么请帮我解决这个问题。
或者我的服务器配置有问题吗?
答案 0 :(得分:211)
如果你这样做,他们应该是相同的。但请注意,浮点值的一个特征是似乎导致相同值的计算不需要实际相同。因此,如果$a
是文字.17
并且$b
通过计算到达那里,则很可能它们是不同的,尽管两者都显示相同的值。
通常你永远不会像这样比较浮点值的相等性,你需要使用最小的可接受差异:
if (abs(($a-$b)/$b) < 0.00001) {
echo "same";
}
类似的东西。
答案 1 :(得分:51)
首先阅读红色警告in the manual。你绝不能比较浮点数是否相等。你应该使用epsilon技术。
例如:
if (abs($a-$b) < PHP_FLOAT_EPSILON) { … }
其中PHP_FLOAT_EPSILON
是常量,代表一个非常小的数字(你必须在7.2之前的旧版本的PHP中定义它)
答案 2 :(得分:24)
或尝试使用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, 3)==0 );
结果:
0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func) : bool(true)
答案 3 :(得分:14)
如前所述,在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)将导致预期结果。
答案 4 :(得分:12)
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,如果left_operand为则返回1 大于right_operand,否则为-1。
答案 5 :(得分:6)
如果您有浮点值来与相等性进行比较,那么这是一种避免操作系统,语言,处理器等 内部舍入 策略风险的简单方法,是比较值的 字符串表示 ,例如:
if ( strval($a) === strval($b)) { … }
在检查相等性时,字符串表示比浮点数要小得多。
答案 6 :(得分:3)
如果你有一个小的,有限数量的小数点是可以接受的,下面的工作很好(尽管性能比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';
}
答案 7 :(得分:3)
这适用于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
}
答案 8 :(得分:2)
以下是比较浮点数或十进制数的解决方案
//$fd['someVal'] = 2.9;
//$i for loop variable steps 0.1
if((string)$fd['someVal']== (string)$i)
{
//Equal
}
将decimal
变量投射到string
,您就可以了。
答案 9 :(得分:2)
对于PHP 7.2,您可以使用PHP_FLOAT_EPSILON(http://php.net/manual/en/reserved.constants.php):
const millisecondsToEvent = userEventTimestamp - userNowtimestamp;
const secondsToEvent = millisecondsToEvent / 1000;
答案 10 :(得分:1)
比较浮点数是否相等有一个简单的O(n)算法。
您必须将每个浮点值转换为字符串,然后使用整数比较运算符比较从每个浮点字符串表示的左侧开始的每个数字。在比较之前,PHP会将每个索引位置的数字自动转换为整数。第一个数字大于另一个数字将打破循环并声明它所属的浮点数为两者中的较大者。平均而言,将进行1/2 * n比较。对于彼此相等的浮点数,将进行n次比较。这是该算法的最坏情况。最好的情况是每个浮点数的第一个数字是不同的,只能进行一次比较。
您不能在原始浮点值上使用INTEGER COMPARISON OPERATORS,以产生有用的结果。这些操作的结果没有意义,因为您没有比较整数。您违反了每个运营商的域名,从而产生无意义的结果。这也适用于delta比较。
使用整数比较运算符来设计它们:比较整数。
简化的解决方案:
<?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;
}
}
?>
答案 11 :(得分:1)
如果你这样写它可能会起作用,所以我想你已经为这个问题简化了它。 (并且保持简单和简洁的问题通常是一件非常好的事情。)
但在这种情况下,我想一个结果是计算,一个结果是一个常数。
这违反了浮点编程的基本规则: 绝不进行等式比较。
其原因有点微妙 1 但要记住的重要一点是,他们通常不工作(具有讽刺意味的是,除了积分值)以及另一种方法是按照以下方式进行模糊比较:
if abs(a - y) < epsilon
1。其中一个主要问题涉及我们在程序中编写数字的方式。我们将它们写成十进制字符串,因此我们编写的大多数分数都没有精确的机器表示。他们没有精确的有限形式,因为它们以二进制重复。每个机器分数是x / 2 n 形式的有理数。现在,常数是十进制的,每个十进制常数是x /(2 n * 5 m )形式的有理数。 5 m 数字是奇数,因此对于它们中的任何一个都没有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。
答案 12 :(得分:1)
@evilReiko中的函数有如下错误:
cmpFloats(-0.1, '==', 0.1); // Expected: false, actual: true
cmpFloats(-0.1, '<', 0.1); // Expected: true, actual: false
cmpFloats(-4, '<', -3); // Expected: true, actual: true
cmpFloats(-5.004, '<', -5.003); // Expected: true, actual: false
在我的函数中,我已修复了这些错误,但是无论如何在某些情况下,此函数返回错误的答案:
cmpFloats(0.0000001, '==', -0.0000001); // Expected: false, actual: true
cmpFloats(843994202.303411, '<', 843994202.303413); // Expected: true, actual: false
cmpFloats(843994202.303413, '>', 843994202.303411); // Expected: true, actual: false
function cmpFloats($a, $operation, $b, $decimals = 15)
{
if ($decimals < 0) {
throw new Exception('Invalid $decimals ' . $decimals . '.');
}
if (!in_array($operation, ['==', '!=', '>', '>=', '<', '<='])) {
throw new Exception('Invalid $operation ' . $operation . '.');
}
$aInt = (int)$a;
$bInt = (int)$b;
$aIntLen = strlen((string)$aInt);
$bIntLen = strlen((string)$bInt);
// We'll not used number_format because it inaccurate with very long numbers, instead will use str_pad and manipulate it as string
$aStr = (string)$a;//number_format($a, $decimals, '.', '');
$bStr = (string)$b;//number_format($b, $decimals, '.', '');
// If passed null, empty or false, then it will be empty string. So change it to 0
if ($aStr === '') {
$aStr = '0';
}
if ($bStr === '') {
$bStr = '0';
}
if (strpos($aStr, '.') === false) {
$aStr .= '.';
}
if (strpos($bStr, '.') === false) {
$bStr .= '.';
}
$aIsNegative = strpos($aStr, '-') !== false;
$bIsNegative = strpos($bStr, '-') !== false;
// Append 0s to the right
$aStr = str_pad($aStr, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);
$bStr = str_pad($bStr, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);
// If $decimals are less than the existing float, truncate
$aStr = substr($aStr, 0, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals);
$bStr = substr($bStr, 0, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals);
$aDotPos = strpos($aStr, '.');
$bDotPos = strpos($bStr, '.');
// Get just the decimal without the int
$aDecStr = substr($aStr, $aDotPos + 1, $decimals);
$bDecStr = substr($bStr, $bDotPos + 1, $decimals);
$aDecLen = strlen($aDecStr);
//$bDecLen = strlen($bDecStr);
// To match 0.* against -0.*
$isBothZeroInts = $aInt == 0 && $bInt == 0;
if ($operation === '==') {
return $aStr === $bStr ||
($isBothZeroInts && $aDecStr === $bDecStr && $aIsNegative === $bIsNegative);
} elseif ($operation === '!=') {
return $aStr !== $bStr ||
$isBothZeroInts && $aDecStr !== $bDecStr;
} elseif ($operation === '>') {
if ($aInt > $bInt) {
return true;
} elseif ($aInt < $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return (!$aIsNegative && $bIsNegative);
}
if ($aDecStr === $bDecStr) {
return false;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
} else {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
}
}
}
}
} elseif ($operation === '>=') {
if ($aInt > $bInt ||
$aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr) {
return true;
} elseif ($aInt < $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return (!$aIsNegative && $bIsNegative);
}
if ($aDecStr === $bDecStr) {// Decimals also equal
return true;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
} else {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
}
}
}
}
} elseif ($operation === '<') {
if ($aInt < $bInt) {
return true;
} elseif ($aInt > $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return ($aIsNegative && !$bIsNegative);
}
if ($aDecStr === $bDecStr) {
return false;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
} else {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
}
}
}
}
} elseif ($operation === '<=') {
if ($aInt < $bInt ||
$aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr) {
return true;
} elseif ($aInt > $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return ($aIsNegative && !$bIsNegative);
}
if ($aDecStr === $bDecStr) {// Decimals also equal
return true;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
} else {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
}
}
}
}
}
}
$a = 1 - 0.83;// 0.17
$b = 0.17;
if($a == $b) {
echo 'same';
} else {
echo 'different';
}
// Output: different (wrong)
if(cmpFloats($a, '==', $b)) {
echo 'same';
} else {
echo 'different';
}
// Output: same (correct)
答案 13 :(得分:1)
使用和决定要使用的小数位数效果很好。数字格式
$a = 0.17;
$b = 1 - 0.83; //0.17
$dec = 2;
if(number_format($a,$dec) == number_format($b,$dec))
{
echo 'a and b are same';
}
else
{
echo 'a and b are not same';
}
//output : a and b are same
答案 14 :(得分:0)
在下面使用我的功能,例如if(cmpFloats($a, '==', $b)) { ... }
cmpFloats($a, '<=', $b)
与bccomp($a, $b) <= -1
我将揭开谜底。
$a = 0.17;
$b = 1 - 0.83;// 0.17 (output)
// but actual value internally is: 0.17000000000000003996802888650563545525074005126953125
if($a == $b) {
echo 'same';
} else {
echo 'different';
}
// Output: different
因此,如果您尝试以下操作,它将等于:
if($b == 0.17000000000000003) {
echo 'same';
} else {
echo 'different';
}
// Output "same"
如何获取浮点数的实际值?
$b = 1 - 0.83;
echo $b;// 0.17
echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000
您如何比较?
==
和!=
进行比较,则可以将它们类型转换为字符串,它应该可以正常工作:类型转换为字符串:
$b = 1 - 0.83;
if((string)$b === (string)0.17) {
echo 'if';
} else {
echo 'else';
}
// it will output "if"
或使用number_format()
进行打字:
$b = 1 - 0.83;
if(number_format($b, 3) === number_format(0.17, 3)) {
echo 'if';
} else {
echo 'else';
}
// it will output "if"
警告:
避免使用涉及对浮点数进行数学运算(乘,除等)然后进行比较的解决方案,大多数情况下,它们会解决一些问题并引入其他问题。
我已经创建了纯PHP函数(不需要依赖/库/扩展)。检查并比较每个数字是否为字符串。也适用于负数。
/**
* Compare numbers (floats, int, string), this function will compare them safely
* @param Float|Int|String $a (required) Left operand
* @param String $operation (required) Operator, which can be: "==", "!=", ">", ">=", "<" or "<="
* @param Float|Int|String $b (required) Right operand
* @param Int $decimals (optional) Number of decimals to compare
* @return boolean Return true if operation against operands is matching, otherwise return false
* @throws Exception Throws exception error if passed invalid operator or decimal
*/
function cmpFloats($a, $operation, $b, $decimals = 15) {
if($decimals < 0) {
throw new Exception('Invalid $decimals ' . $decimals . '.');
}
if(!in_array($operation, ['==', '!=', '>', '>=', '<', '<='])) {
throw new Exception('Invalid $operation ' . $operation . '.');
}
$aInt = (int)$a;
$bInt = (int)$b;
$aIntLen = strlen((string)$aInt);
$bIntLen = strlen((string)$bInt);
// We'll not used number_format because it inaccurate with very long numbers, instead will use str_pad and manipulate it as string
$aStr = (string)$a;//number_format($a, $decimals, '.', '');
$bStr = (string)$b;//number_format($b, $decimals, '.', '');
// If passed null, empty or false, then it will be empty string. So change it to 0
if($aStr === '') {
$aStr = '0';
}
if($bStr === '') {
$bStr = '0';
}
if(strpos($aStr, '.') === false) {
$aStr .= '.';
}
if(strpos($bStr, '.') === false) {
$bStr .= '.';
}
$aIsNegative = strpos($aStr, '-') !== false;
$bIsNegative = strpos($bStr, '-') !== false;
// Append 0s to the right
$aStr = str_pad($aStr, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);
$bStr = str_pad($bStr, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);
// If $decimals are less than the existing float, truncate
$aStr = substr($aStr, 0, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals);
$bStr = substr($bStr, 0, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals);
$aDotPos = strpos($aStr, '.');
$bDotPos = strpos($bStr, '.');
// Get just the decimal without the int
$aDecStr = substr($aStr, $aDotPos + 1, $decimals);
$bDecStr = substr($bStr, $bDotPos + 1, $decimals);
$aDecLen = strlen($aDecStr);
//$bDecLen = strlen($bDecStr);
// To match 0.* against -0.*
$isBothZeroInts = $aInt == 0 && $bInt == 0;
if($operation === '==') {
return $aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr;
} else if($operation === '!=') {
return $aStr !== $bStr ||
$isBothZeroInts && $aDecStr !== $bDecStr;
} else if($operation === '>') {
if($aInt > $bInt) {
return true;
} else if($aInt < $bInt) {
return false;
} else {// Ints equal, check decimals
if($aDecStr === $bDecStr) {
return false;
} else {
for($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if($aD > $bD) {
return true;
} else if($aD < $bD) {
return false;
}
}
}
}
} else if($operation === '>=') {
if($aInt > $bInt ||
$aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr) {
return true;
} else if($aInt < $bInt) {
return false;
} else {// Ints equal, check decimals
if($aDecStr === $bDecStr) {// Decimals also equal
return true;
} else {
for($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if($aD > $bD) {
return true;
} else if($aD < $bD) {
return false;
}
}
}
}
} else if($operation === '<') {
if($aInt < $bInt) {
return true;
} else if($aInt > $bInt) {
return false;
} else {// Ints equal, check decimals
if($aDecStr === $bDecStr) {
return false;
} else {
for($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if($aD < $bD) {
return true;
} else if($aD > $bD) {
return false;
}
}
}
}
} else if($operation === '<=') {
if($aInt < $bInt ||
$aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr) {
return true;
} else if($aInt > $bInt) {
return false;
} else {// Ints equal, check decimals
if($aDecStr === $bDecStr) {// Decimals also equal
return true;
} else {
for($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if($aD < $bD) {
return true;
} else if($aD > $bD) {
return false;
}
}
}
}
}
}
$a = 1 - 0.83;// 0.17
$b = 0.17;
if($a == $b) {
echo 'same';
} else {
echo 'different';
}
// Output: different (wrong)
if(cmpFloats($a, '==', $b)) {
echo 'same';
} else {
echo 'different';
}
// Output: same (correct)
答案 15 :(得分:0)
这是我的个人图书馆中一个有用的类,用于处理浮点数。您可以根据自己的喜好将其进行调整,然后将所需的任何解决方案插入类方法中:-)。
/**
* A class for dealing with PHP floating point values.
*
* @author Anthony E. Rutledge
* @version 12-06-2018
*/
final class Float extends Number
{
// PHP 7.4 allows for property type hints!
private const LESS_THAN = -1;
private const EQUAL = 0;
private const GREATER_THAN = 1;
public function __construct()
{
}
/**
* Determines if a value is an float.
*
* @param mixed $value
* @return bool
*/
public function isFloat($value): bool
{
return is_float($value);
}
/**
* A method that tests to see if two float values are equal.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function equals(float $y1, float $y2): bool
{
return (string) $y1 === (string) $y2;
}
/**
* A method that tests to see if two float values are not equal.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isNotEqual(float $y1, float $y2): bool
{
return !$this->equals($y1, $y2);
}
/**
* Gets the bccomp result.
*
* @param float $y1
* @param float $y2
* @return int
*/
private function getBccompResult(float $y1, float $y2): int
{
$leftOperand = (string) $y1;
$rightOperand = (string) $y2;
// You should check the format of the float before using it.
return bccomp($leftOperand, $rightOperand);
}
/**
* A method that tests to see if y1 is less than y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isLess(float $y1, float $y2): bool
{
return ($this->getBccompResult($y1, $y2) === self::LESS_THAN);
}
/**
* A method that tests to see if y1 is less than or equal to y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isLessOrEqual(float $y1, float $y2): bool
{
$bccompResult = $this->getBccompResult($y1, $y2);
return ($bccompResult === self::LESS_THAN || $bccompResult === self::EQUALS);
}
/**
* A method that tests to see if y1 is greater than y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isGreater(float $y1, float $y2): bool
{
return ($this->getBccompResult($y1, $y2) === self::GREATER_THAN);
}
/**
* A method that tests to see if y1 is greater than or equal to y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isGreaterOrEqual(float $y1, float $y2): bool
{
$bccompResult = $this->getBccompResult($y1, $y2);
return ($bccompResult === self::GREATER_THAN || $bccompResult === self::EQUALS);
}
/**
* Returns a valid PHP float value, casting if necessary.
*
* @param mixed $value
* @return float
*
* @throws InvalidArgumentException
* @throws UnexpectedValueException
*/
public function getFloat($value): float
{
if (! (is_string($value) || is_int($value) || is_bool($value))) {
throw new InvalidArgumentException("$value should not be converted to float!");
}
if ($this->isFloat($value)) {
return $value;
}
$newValue = (float) $value;
if ($this->isNan($newValue)) {
throw new UnexpectedValueException("The value $value was converted to NaN!");
}
if (!$this->isNumber($newValue)) {
throw new UnexpectedValueException("The value $value was converted to something non-numeric!");
}
if (!$this->isFLoat($newValue)) {
throw new UnexpectedValueException("The value $value was not converted to a floating point value!");
}
return $newValue;
}
}
?>
答案 16 :(得分:0)
简单答案:
if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }