2019
TL; DR
ใช้ฟังก์ชั่นของฉันด้านล่างเช่นนี้ if(cmpFloats($a, '==', $b)) { ... }
- ง่ายต่อการอ่าน / เขียน / เปลี่ยนแปลง:
cmpFloats($a, '<=', $b)
vsbccomp($a, $b) <= -1
- ไม่จำเป็นต้องพึ่งพา
- ทำงานร่วมกับ PHP เวอร์ชันใดก็ได้
- ทำงานร่วมกับตัวเลขลบ
- ทำงานร่วมกับทศนิยมที่ยาวที่สุดที่คุณสามารถจินตนาการได้
- ข้อเสีย: ช้ากว่า bccomp เล็กน้อย ()
สรุป
ฉันจะเปิดเผยความลึกลับ
$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
คุณเปรียบเทียบได้อย่างไร
- ใช้ฟังก์ชั่น BC คณิตศาสตร์ (คุณจะยังได้รับช่วงเวลา wtf-aha-gotcha จำนวนมาก)
- คุณอาจลองตอบ @ @ Gladhon โดยใช้ PHP_FLOAT_EPSILON (PHP 7.2)
- หากเปรียบเทียบการลอยด้วย
==
และ!=
คุณสามารถพิมพ์ลงในสตริงได้ควรทำงานได้อย่างสมบูรณ์:
ประเภท cast ด้วยสตริง :
$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)
a and b are same
ฉันได้รับ เป็นรหัสเต็มของคุณหรือไม่