Compare floats in php
Asked Answered
T

17

194

I want to compare two floats in PHP, like in this sample code:

$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';
}

In this code it returns the result of the else condition instead of the if condition, even though $a and $b are same. Is there any special way to handle/compare floats in PHP?

If yes then please help me to solve this issue.

Or is there a problem with my server config?

Turf answered 30/6, 2010 at 11:51 Comment(4)
I get a and b are same. Is this your full code?Shears
what version? it works fine for me.Bedpost
@Andrey this is probably it because the real world case is likely to be more complex than the example quoted. Why not add it as an answer?Shears
Did you read the floating-point tag description? stackoverflow.com/tags/floating-point/info That's a behavior you'd likely encounter in any programming language, when using floating-point numbers. See e.g. https://mcmap.net/q/15696/-is-floating-point-math-brokenChatelain
C
267

If you do it like this they should be the same. But note that a characteristic of floating-point values is that calculations which seem to result in the same value do not need to actually be identical. So if $a is a literal .17 and $b arrives there through a calculation it can well be that they are different, albeit both display the same value.

Usually you never compare floating-point values for equality like this, you need to use a smallest acceptable difference:

if (abs(($a-$b)/$b) < 0.00001) {
  echo "same";
}

Something like that.

Circus answered 30/6, 2010 at 11:57 Comment(9)
BEWARE! Choosing a fixed epsilon is a bad way just because it looks small, this comparison will return true in alot of precision errors when the numbers are small. A correct way would be to check if the relative error is smaller than the epsilon. abs($a-$b) > abs(($a-$b)/$b)Whitherward
Yes, I intentionally simplified here.Circus
So let me understand, PHP prints both numbers the same way but internally considers them different, right? This is quite weird, because I sopose if internally they are different, PHP should also print them different, so we can see the difference and not waste time. Know what I mean? Does it make any sense?Easing
@Alexandru: I know what you mean, but PHP isn't alone in that regard. You need to distinguish two use cases here: Showing a number to a user. In that case displaying 0.10000000000000000555111512312578270211815834045410156 is usually pointless and they'd prefer 0.1 instead. And writing a number so that it can be read again in exactly the same way. As you see, it's not as clear-cut as you make it out to be. And for the record, you still want to compare floating-point numbers like I have shown because you can arrive at $a and $b via different calculations that can make them different.Circus
There are still some edge cases which this test fails. Such as a=b=0 and if a is the smallest possible none zero positive value and b is the smallest possible non zero negative value, the test will incorrectly fail. Some good information here: floating-point-gui.de/errors/comparisonForbes
Why divide on $b ? the PHP manual just did if(abs($a-$b) < $epsilon) the MySQL manual also did the same HAVING ABS(a - b) <= 0.0001Kino
I agree with Accountant, this is not correct answer because you are dividing with $b.Honna
@CaslavSabani: This is relative, not absolute error. It's still broken (especially when $a == $b == 0, but it's already a lot more general than absolute error. If $a and $b are in the millions, then your EPSILON would have to be very different than if $a and $b are somewhere close to 0. See Dom's link above for a better discussion of this.Circus
that thing with dividing by b to get relative error actually that piete suggested doesn't workLapland
I
104

Read the red warning in the manual first. You must never compare floats for equality. You should use the epsilon technique.

For example:

if (abs($a-$b) < PHP_FLOAT_EPSILON) { … }

where PHP_FLOAT_EPSILON is constant representing a very small number (you have to define it in old versions of PHP before 7.2)

Institution answered 30/6, 2010 at 11:59 Comment(6)
To clarify, is EPSILON in this case the machine epsilon, which is roughly 2.2204460492503E-16? And, does this comparison work for two floats of any magnitude?Seasonal
@MichaelCordingley No, EPSILON here is an arbitrary user-defined constant. PHP does not have a built-in constant representing an architecture's specific idea of epsilon. (See also get_defined_constants.)Ovariectomy
PHP_FLOAT_EPSILON Smallest representable positive number x, so that x + 1.0 != 1.0. Available as of PHP 7.2.0.Insist
This does not actually work in this case: $a = 270.10 + 20.10; $b = 290.20; if (abs($a-$b) < PHP_FLOAT_EPSILON){ echo 'same'; }Dais
@Dais because those expressions produce different numbers. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */ The question is how you want to define "equal" for your application, how close the numbers should be to be considered equal.Institution
the PHP EPSILON is too small to work, it's seems simple calculations generate a bigger delta than that epsilon, returning false for something that is pretty close but not as close as the minimum allowed in PHP.Prink
E
35

Or try to use bc math functions:

<?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 );

Result:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)
Extract answered 27/12, 2012 at 10:15 Comment(3)
in your usage of bccomp you've missed the "scale" thus you're actually comparing 0 to 0 according to the manual: php.net/manual/en/function.bccomp.phpDoro
I'm liking this. Most solutions seem to rely on rounding and losing precision, but I'm dealing with latitude and longitude coordinates with 12 points of precision and this seems to compare them accurately with no tweaking needed.Canossa
What about performance? bccomp() takes strings as arguments. Anyway you could use PHP_FLOAT_DIG for the scale argument.Insist
S
24

As said before, be very careful when doing floating point comparisons (whether equal-to, greater-than, or less-than) in PHP. However if you're only ever interested in a few significant digits, you can do something like:

$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';
}

The use of rounding to 2 decimal places (or 3, or 4) will cause the expected result.

Stovall answered 10/7, 2014 at 18:7 Comment(3)
Extra word of warning, I wouldn't recommend littering your codebase with statements like these. If you want to do a loose float comparison, make a method like loose_float_compare so it's obvious what is going on.Stovall
PHP's native bccomp($a, $b, 2) is superior to your solution. In this example, the 2 is the precision. you can set it to whatever number of floating points you want to compare.Isauraisbel
@JohnMiller I'm not disagreeing with you, but bccomp isn't available by default. It requires either a compilation flag to be enabled, or an extension installed. Not part of core.Stovall
S
23

If you have floating point values to compare to equality, a simple way to avoid the risk of internal rounding strategy of the OS, language, processor or so on, is to compare the string representation of the values.

You can use any of the following to produce the desired result: https://3v4l.org/rUrEq

String Type Casting

if ( (string) $a === (string) $b) { … }

String Concatenation

if ('' . $a === '' . $b) { … }

strval function

if (strval($a) === strval($b)) { … }

String representations are much less finicky than floats when it comes to checking equality.

Sheepherder answered 7/11, 2014 at 10:48 Comment(6)
or if ( strval($a) === strval($b)) { … } if you don't want to convert the original valuesLothar
Well, my original answer was: if (''.$a === ''.$b) { … } but someone edited it. So...Sheepherder
@Ekonoval Could you please elaborate to your modification, It appears that you are asserting that the (string) cast operation is performed by-reference, changing the original declaration? If so that is not the case 3v4l.org/CraasBenares
@fyrye Yeah I guess I was wrong, the both approaches yield the same result.Lothar
Updated the answer to give an example usage and all of the examples of the other edits along with the originalBenares
Note that this will not always work as expected when mixing types! Try with $a = '1.000'; $b = 1; which is often the case when a DECIMAL is fetched from db, while the other value is calculatedOutoftheway
S
23

It would be better to use native PHP comparison:

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. 

Returns 0 if the two operands are equal, 1 if the left_operand is larger than the right_operand, -1 otherwise.

Stgermain answered 21/6, 2017 at 11:1 Comment(3)
It's worth noting that the bc functions all truncate, rather than round. You may be expecting, for example, bccomp(0.9999999, 1.000000, 2) to return zero, but this will return -1.Wonderwork
@Cynewulf, for me it's a correct behavior. If not in yours, use PHP_FLOAT_EPSILONStgermain
Should also be noted that bccomp uses strings as the first two argumentsGoose
S
7

This works for me on 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
}
Shool answered 23/7, 2014 at 9:23 Comment(0)
I
5

If you have a small, finite number of decimal points that will be acceptable, the following works nicely (albeit with slower performance than the epsilon solution):

$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';
}
Indian answered 9/4, 2015 at 1:41 Comment(0)
G
5

For PHP 7.2, you can work with PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ):

if(abs($a-$b) < PHP_FLOAT_EPSILON){
   echo 'a and b are same';
}
Germ answered 1/3, 2018 at 14:17 Comment(1)
Good solution. But: 1- Requires updating PHP 7.2 which not everyone can do easily for existing big/old systems 2- This works only for == and != but not >, >=, <, <=Kristeenkristel
T
4

Here is the solution for comparing floating points or decimal numbers

//$fd['someVal'] = 2.9;
//$i for loop variable steps 0.1
if((string)$fd['someVal']== (string)$i)
{
    //Equal
}

Cast a decimal variable to string and you will be fine.

Trimetric answered 11/5, 2014 at 11:47 Comment(0)
S
4

Works great using and deciding the number of decimal places to use. number_format

$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';
}

This will also work (contrary to other methods such as string cast) in cases when the type of the two variables is mixed, such as:

$a = '1.0000'; // Fetched from a DECIMAL db column
$b = 1;
Susceptible answered 21/5, 2021 at 12:50 Comment(0)
F
2

If you write it just like that it will probably work, so I imagine you've simplified it for the question. (And keeping the question simple and concise is normally a very good thing.)

But in this case I imagine one result is a calculation and one result is a constant.

This violates a cardinal rule of floating point programming: Never do equality comparisons.

The reasons for this are a bit subtle1 but what's important to remember is that they usually don't work (except, ironically, for integral values) and that the alternative is a fuzzy comparison along the lines of:

if abs(a - y) < epsilon



1. One of the major problems involves the way we write numbers in programs. We write them as decimal strings, and as a result most of the fractions we write do not have exact machine representations. They don't have exact finite forms because they repeat in binary. Every machine fraction is a rational number of the form x/2n. Now, the constants are decimal and every decimal constant is a rational number of the form x/(2n * 5m). The 5m numbers are odd, so there isn't a 2n factor for any of them. Only when m == 0 is there a finite representation in both the binary and decimal expansion of the fraction. So, 1.25 is exact because it's 5/(22*50) but 0.1 is not because it's 1/(20*51). In fact, in the series 1.01 .. 1.99 only 3 of the numbers are exactly representable: 1.25, 1.50, and 1.75.

Ferdinand answered 26/3, 2012 at 6:27 Comment(2)
DigitalRoss its quite difficult to understand few terms in your comment, but yes, its very informative. And I am going to google these terms. Thanks :)Turf
Isn't it safe enough to do comparisons on floats, provided you are rounding the result every time and are within a few significant digits? In other words round($float, 3) == round($other, 3)Stovall
E
2

Comparing floats for equality has a naive O(n) algorithm.

You must convert each float value to a string, then compare each digit starting from the left side of each float's string representation using integer comparison operators. PHP will autocast the digit in each index position to an integer before the comparison. The first digit larger than the other will break the loop and declare the float that it belongs to as the greater of the two. On average, there will be 1/2 * n comparisons. For floats equal to each other, there will be n comparisons. This is the worst case scenario for the algorithm. The best case scenario is that the first digit of each float is different, causing only one comparison.

You cannot use INTEGER COMPARISON OPERATORS on raw float values with the intention of generating useful results. The results of such operations have no meaning because you are not comparing integers. You are violating the domain of each operator which generates meaningless results. This holds for delta comparison as well.

Use integer comparison operators for what they are designed for : comparing integers.

SIMPLIFIED SOLUTION:

<?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;
  }
 }

?>
Epicarp answered 16/9, 2015 at 20:53 Comment(0)
K
2

2019

TL;DR

Use my function below, like this if(cmpFloats($a, '==', $b)) { ... }

  • Easy to read/write/change: cmpFloats($a, '<=', $b) vs bccomp($a, $b) <= -1
  • No dependencies needed.
  • Works with any PHP version.
  • Works with negative numbers.
  • Works with the longest decimal you can imagine.
  • Downside: Slightly slower than bccomp()

Summary

I'll unveil the mystery.

$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

So if you try the below, it will be equal:

if($b == 0.17000000000000003) {
    echo 'same';
} else {
    echo 'different';
}
// Output "same"

How to get the actual value of float?

$b = 1 - 0.83;
echo $b;// 0.17
echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000

How can you compare?

  1. Use BC Math functions. (you'll still get a lot of wtf-aha-gotcha moments)
  2. You may try @Gladhon's answer, using PHP_FLOAT_EPSILON (PHP 7.2).
  3. If comparing floats with == and !=, you can typecast them to strings, it should work perfectly:

Type cast with string:

$b = 1 - 0.83;
if((string)$b === (string)0.17) {
    echo 'if';
} else {
    echo 'else';
}
// it will output "if"

Or typecast with 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"

Warning:

Avoid solutions that involves manipulating floats mathematically (multiplying, dividing, etc) then comparing, mostly they'll solve some problems and introduce other problems.


Suggested Solution

I've create pure PHP function (no depenedcies/libraries/extensions needed). Checks and compares each digit as string. Also works with negative numbers.

/**
 * 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)
Kristeenkristel answered 25/2, 2019 at 6:10 Comment(2)
Delete this.,,,Alveta
@QuolonelQuestions Why so?Kristeenkristel
W
1

Function from @evilReiko have some bugs like these:

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

In my function I've fixed these bugs, but anyway in some cases this function return wrong answers:

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

Fixed function for compare floats

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;
                        }
                    }
                }
            }
        }
    }
}

Answer for your question

$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)
Wisnicki answered 26/12, 2019 at 12:25 Comment(0)
U
1

Simple answer:

if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
Unscreened answered 28/2, 2020 at 13:34 Comment(1)
If PHP can do their job properly on evaluating float value, there is no way people make long process to compare float value here.Beltane
F
0

Here is a useful class from my personal library for dealing with floating point numbers. You can tweek it to your liking and insert any solution you like into the class methods :-).

/**
 * 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;
    }
}
?>
Frolic answered 6/12, 2019 at 23:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.