比较php中的浮点值
我想比较PHP中的两个浮点值,如下面的示例代码:比较php中的浮点值,php,floating-point,Php,Floating Point,我想比较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中有没有处理/比较浮点的特殊方法 如果是,请帮助我解决这个问题 或者我的服务器配置有问题吗?如果这样做,它们应该是相同的。但请注意,
$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中有没有处理/比较浮点的特殊方法
如果是,请帮助我解决这个问题
或者我的服务器配置有问题吗?如果这样做,它们应该是相同的。但请注意,浮点值的一个特点是,似乎产生相同值的计算不需要实际相同。因此,如果
$a
是一个文本.17
并且$b
通过计算到达那里,很可能它们是不同的,尽管两者显示相同的值
通常情况下,您不会像这样比较浮点值是否相等,您需要使用最小的可接受差异:
if (abs(($a-$b)/$b) < 0.00001) {
echo "same";
}
if(绝对值($a-$b)/$b)<0.00001){
呼应“相同”;
}
类似的东西。首先阅读红色警告。决不能比较浮点数是否相等。你应该使用ε技术
例如:
if (abs($a-$b) < PHP_FLOAT_EPSILON) { … }
if(abs($a-$b)
其中,
PHP\u FLOAT\u EPSILON
是一个常量,表示一个非常小的数字(您必须在7.2之前的旧版本中定义它)如果您这样编写它,它可能会工作,所以我想您已经简化了这个问题。(保持问题的简洁通常是一件非常好的事情。)
但在这种情况下,我想象一个结果是计算,一个结果是常数
这违反了浮点编程的一条基本规则:永远不要进行相等比较。
原因有点微妙,但重要的是要记住,它们通常不起作用(具有讽刺意味的是,对于整数值除外),而另一种选择是沿着以下路线进行模糊比较:
if abs(a - y) < epsilon
如果abs(a-y)
1.主要问题之一涉及我们在程序中写入数字的方式。我们把它们写成十进制字符串,因此我们写的大多数分数没有精确的机器表示。它们没有精确的有限形式,因为它们以二进制形式重复。每个机器分数都是形式为x/2n的有理数。现在,常数是十进制的,每个十进制常数都是形式为x/(2n*5m)的有理数。这5百万个数字是奇数,因此它们中的任何一个都没有2n因子。只有当m==0时,分数的二进制和十进制展开式才有有限表示。1.25是精确的,因为它是5/(22*50),但0.1不是因为它是1/(20*51)。事实上,在1.01系列中。。1.99只有3个数字可以精确表示:1.25、1.50和1.75。或尝试使用bc数学函数:
这里是比较浮点数或十进制数的解决方案
//$fd['someVal'] = 2.9;
//$i for loop variable steps 0.1
if((string)$fd['someVal']== (string)$i)
{
//Equal
}
将decimal
变量转换为string
就可以了。如前所述,在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位)将导致预期结果 这对我来说在PHP5.3.27上很有效
$payments_total = 123.45;
$order_total = 123.45;
if (round($payments_total, 2) != round($order_total, 2)) {
// they don't match
}
如果要将浮点值与相等值进行比较,一种避免操作系统、语言、处理器等的内部舍入策略风险的简单方法是比较值的字符串表示形式
您可以使用以下任意一项来生成所需的结果:
弦式铸造
if((字符串)$a==(字符串)$b){…}
字符串串联
if('.$a=='.$b){…}
标准函数
if(strval($a)==strval($b)){…}
在检查相等性时,字符串表示法远没有浮点表示法那么挑剔。如果小数点的数量很小且有限,则以下方法可以很好地工作(尽管性能比epsilon解决方案慢):
比较浮点数是否相等有一个简单的O(n)算法
必须将每个浮点值转换为字符串,然后使用整数比较运算符从每个浮点的字符串表示的左侧开始比较每个数字。PHP将在比较之前将每个索引位置的数字自动广播为整数。第一个比另一个大的数字将中断循环,并将它所属的浮点声明为两个数字中较大的一个。平均而言,会有1/2*n的比较。对于彼此相等的浮点,将有n个比较。这是算法的最坏情况。最好的情况是,每个浮点的第一个数字不同,只会导致一次比较
不能为了生成有用的结果而对原始浮点值使用整数比较运算符。这些操作的结果没有意义,因为您没有比较整数。您违反了每个运算符的域,这会产生无意义的结果。这也适用于增量比较
将整数比较运算符用于它们的设计目的:比较整数
简化解决方案:
<?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;
}
}
?>
最好使用:
如果两个操作数相等,则返回0;如果左/右操作数相等,则返回1
大于右操作数,否则为-1
对于PHP7.2,可以使用PHP_FLOAT_EPSILON():
if(abs($a-$b)
2019
TL;博士
使用下面的my函数,如if(cmpFloats($a,'==',$b)){…}
- 易于阅读/编写/更改:
cmpFloats($a),这里是我个人库中一个有用的类,用于处理floa
$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';
}
<?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;
}
}
?>
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.
if(abs($a-$b) < PHP_FLOAT_EPSILON){
echo 'a and b are same';
}
$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"
$b = 1 - 0.83;
if(number_format($b, 3) === number_format(0.17, 3)) {
echo 'if';
} else {
echo 'else';
}
// it will output "if"
/**
* 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)
/**
* 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;
}
}
?>
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)
if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
$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