Compare floats in php

后端 未结 16 1642
清歌不尽
清歌不尽 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: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)
    

提交回复
热议问题