Compare floats in php

后端 未结 16 1641
清歌不尽
清歌不尽 2020-11-22 00:47

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         


        
相关标签:
16条回答
  • 2020-11-22 01:35

    Simple answer:

    if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
    
    0 讨论(0)
  • 2020-11-22 01:36

    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';
    }
    
    0 讨论(0)
  • 2020-11-22 01:42

    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;
      }
     }
    
    ?>
    
    0 讨论(0)
  • 2020-11-22 01:42

    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)
    
    0 讨论(0)
提交回复
热议问题