So sánh số float trong php


155

Tôi muốn so sánh hai float trong PHP, như trong mã mẫu này:

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

Trong mã này, nó trả về kết quả của elseđiều kiện thay vì ifđiều kiện, mặc dù $a$bgiống nhau. Có cách nào đặc biệt để xử lý / so sánh các float trong PHP không?

Nếu có thì xin hãy giúp tôi giải quyết vấn đề này.

Hoặc có vấn đề với cấu hình máy chủ của tôi?


Tôi nhận được a and b are same. Đây có phải là mã đầy đủ của bạn?
Pekka

phiên bản nào Việc này ổn với tôi.
gblazex

@Andrey điều này có lẽ là vì trường hợp thực tế có thể phức tạp hơn so với ví dụ được trích dẫn. Tại sao không thêm nó như một câu trả lời?
Pekka

2
Bạn đã đọc floating-pointmô tả thẻ? stackoverflow.com/tags/floating-point/info Đó là hành vi bạn có thể gặp phải trong bất kỳ ngôn ngữ lập trình nào, khi sử dụng số dấu phẩy động. Xem ví dụ stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor rời khỏi tòa nhà

Câu trả lời:


232

Nếu bạn làm như thế này thì họ sẽ giống nhau. Nhưng lưu ý rằng một đặc điểm của các giá trị dấu phẩy động là các phép tính dường như dẫn đến cùng một giá trị không cần phải thực sự giống hệt nhau. Vì vậy, nếu $alà một nghĩa đen .17$bđến đó thông qua một phép tính thì cũng có thể là chúng khác nhau, mặc dù cả hai đều hiển thị cùng một giá trị.

Thông thường, bạn không bao giờ so sánh các giá trị dấu phẩy động cho đẳng thức như thế này, bạn cần sử dụng mức chênh lệch nhỏ nhất có thể chấp nhận:

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

Một cái gì đó như thế.


21
HÃY THỬ! Chọn một epsilon cố định là một cách tồi chỉ vì nó trông nhỏ, sự so sánh này sẽ trả về đúng với rất nhiều lỗi chính xác khi các số nhỏ. Một cách chính xác sẽ là kiểm tra xem lỗi tương đối có nhỏ hơn epsilon hay không. abs($a-$b)>abs(($a-$b)/$b)
Piet bijl

1
@Alexandru: Tôi hiểu ý của bạn, nhưng PHP không đơn độc trong vấn đề đó. Bạn cần phân biệt hai trường hợp sử dụng ở đây: Hiển thị số cho người dùng. Trong trường hợp đó, hiển thị 0.10000000000000000555111512312578270211815834045410156thường là vô nghĩa và 0.1thay vào đó họ thích hơn . Và viết một số để nó có thể được đọc lại theo cùng một cách chính xác. Như bạn thấy, nó không rõ ràng như bạn nghĩ. Và đối với bản ghi, bạn vẫn muốn so sánh các số dấu phẩy động như tôi đã chỉ ra bởi vì bạn có thể đến $a$bthông qua các phép tính khác nhau có thể làm cho chúng khác nhau.
Joey

2
Vẫn còn một số trường hợp cạnh mà thử nghiệm này thất bại. Chẳng hạn như a=b=0và nếu alà nhỏ nhất có thể không có giá trị dương 0 và blà giá trị âm không bằng 0 nhỏ nhất có thể, thử nghiệm sẽ thất bại không chính xác. Một số thông tin tốt ở đây: float-point-gui.de/errors/comparison
Dom

13
Tại sao phải chia $b? các hướng dẫn PHP chỉ làm if(abs($a-$b) < $epsilon) các thủ MySQL cũng đã làm tương tựHAVING ABS(a - b) <= 0.0001
Kế toán م

1
@CaslavSabani: Đây là lỗi tương đối, không phải là lỗi tuyệt đối. Nó vẫn bị hỏng (đặc biệt là khi $a == $b == 0, nhưng nó đã chung chung hơn nhiều so với lỗi tuyệt đối. Nếu $a$btrong hàng triệu, thì bạn EPSILONsẽ phải rất khác so với $a$bở đâu đó gần 0. Xem liên kết của Dom ở trên để thảo luận rõ hơn về này.
Joey

64

Đọc cảnh báo màu đỏ trong hướng dẫn trước. Bạn không bao giờ phải so sánh phao cho bình đẳng. Bạn nên sử dụng kỹ thuật epsilon.

Ví dụ:

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

nơi PHP_FLOAT_EPSILONlà hằng số đại diện cho một số lượng rất nhỏ (bạn phải xác định nó trong các phiên bản cũ của PHP trước 7.2)


2
Để làm rõ, có phải EPSILON trong trường hợp này là máy epsilon, tương đương 2.2204460492503E-16? Và, sự so sánh này có hiệu quả đối với hai chiếc phao có độ lớn nào không?
Michael Cplyley

1
@MichaelCplyley Không, EPSILONđây là một hằng số do người dùng định nghĩa. PHP không có hằng số tích hợp thể hiện ý tưởng cụ thể của kiến ​​trúc về epsilon. (Xem thêm get_defined_constants.)
giám mục

5
PHP_FLOAT_EPSILONSố dương nhỏ nhất có thể biểu thị x, sao cho x + 1.0! = 1.0. Có sẵn kể từ phiên bản 7.2.0.
Code4R7

2
Điều này không thực sự hoạt động trong trường hợp này: $ a = 270.10 + 20.10; $ b = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'giống nhau'; }
NemoXP

@NemoXP vì những biểu thức đó tạo ra các số khác nhau. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */Câu hỏi là làm thế nào bạn muốn xác định "bằng" cho ứng dụng của mình, mức độ gần như thế nào để được coi là bằng nhau.
Andrey

28

Hoặc thử sử dụng các hàm toán học bc:

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

Kết quả:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)

2
trong việc bạn sử dụng bccomp bạn đã bỏ lỡ những "quy mô" như vậy, bạn đang thực sự so sánh 0-0 theo hướng dẫn: php.net/manual/en/function.bccomp.php
stefancarlton

Tôi thích điều này. Hầu hết các giải pháp dường như dựa vào làm tròn và mất độ chính xác, nhưng tôi đang xử lý các tọa độ kinh độ và vĩ độ với 12 điểm chính xác và điều này dường như so sánh chúng một cách chính xác mà không cần điều chỉnh.
Rikaelus

Hiệu suất thì sao? bccomp()lấy các chuỗi làm đối số. Dù sao bạn có thể sử dụng PHP_FLOAT_DIGcho các đối số quy mô.
Code4R7

18

Như đã nói trước đây, hãy thật cẩn thận khi thực hiện các phép so sánh dấu phẩy động (cho dù bằng, lớn hơn hoặc nhỏ hơn) trong PHP. Tuy nhiên, nếu bạn chỉ quan tâm đến một vài chữ số có nghĩa, bạn có thể làm một cái gì đó như:

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

Việc sử dụng làm tròn đến 2 chữ số thập phân (hoặc 3 hoặc 4) sẽ gây ra kết quả mong đợi.


1
Thêm lời cảnh báo, tôi không khuyên bạn nên xả rác cơ sở mã của bạn bằng các câu như thế này. Nếu bạn muốn thực hiện một phép so sánh float lỏng lẻo, hãy tạo một phương thức như loose_float_comparevậy để biết rõ điều gì đang xảy ra.
Michael Butler

Bản địa của PHP bccomp($a, $b, 2)là vượt trội so với giải pháp của bạn. Trong ví dụ này, 2 là độ chính xác. bạn có thể đặt nó thành bất kỳ số lượng điểm nổi nào bạn muốn so sánh.
John Miller

@JohnMiller Tôi không đồng ý với bạn, nhưng bccomp không có sẵn theo mặc định. Nó yêu cầu một cờ biên dịch được kích hoạt hoặc một phần mở rộng được cài đặt. Không phải là một phần của cốt lõi.
Michael Butler

17

Sẽ tốt hơn khi sử dụng so sánh PHP gốc :

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. 

Trả về 0 nếu hai toán hạng bằng nhau, 1 nếu left_operand lớn hơn right_operand, -1 nếu không.


10

Nếu bạn có các giá trị dấu phẩy động để so sánh với đẳng thức, một cách đơn giản để tránh rủi ro chiến lược làm tròn nội bộ của HĐH, ngôn ngữ, bộ xử lý, v.v., là so sánh biểu diễn chuỗi của các giá trị.

Bạn có thể sử dụng bất kỳ cách nào sau đây để tạo ra kết quả mong muốn: https://3v4l.org/rUrEq

Đúc kiểu chuỗi

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

Nối chuỗi

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

hàm strval

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

Các biểu diễn chuỗi ít phức tạp hơn nhiều so với float khi nói đến việc kiểm tra sự bằng nhau.


hoặc if (strval ($ a) === strval ($ b)) {Khác} nếu bạn không muốn chuyển đổi các giá trị ban đầu
Ekonoval 18/03/19

Chà, câu trả lời ban đầu của tôi là: if (''. $ A === ''. $ B) {Khăn} nhưng ai đó đã chỉnh sửa nó. Vì vậy ...
Ame Nomade

1
@Ekonoval Bạn có thể giải thích rõ hơn về sửa đổi của mình không, có vẻ như bạn đang khẳng định rằng (string)thao tác diễn viên được thực hiện bằng cách tham chiếu, thay đổi khai báo ban đầu? Nếu vậy đó không phải là trường hợp 3v4l.org/Craas
fyrye

@fyrye Vâng tôi đoán tôi đã sai, cả hai cách tiếp cận đều cho kết quả như nhau.
Ekonoval

Đã cập nhật câu trả lời để đưa ra cách sử dụng ví dụ và tất cả các ví dụ về các chỉnh sửa khác cùng với bản gốc
fyrye

4

Nếu bạn có số điểm thập phân nhỏ, hữu hạn sẽ được chấp nhận, các cách sau sẽ hoạt động tốt (mặc dù có hiệu suất chậm hơn so với giải pháp epsilon):

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

4

Điều này làm việc cho tôi trên 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
}

3

Đối với PHP 7.2, bạn có thể làm việc với PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ):

if(abs($a-$b) < PHP_FLOAT_EPSILON){
   echo 'a and b are same';
}

Giải pháp tốt. Nhưng: 1- Yêu cầu cập nhật PHP 7.2 mà không phải ai cũng có thể làm dễ dàng cho / hệ thống lớn tuổi hiện 2- này hoạt động chỉ dành cho ==!=nhưng không >, >=, <,<=
evilReiko

2

Nếu bạn viết nó giống như vậy thì nó có thể sẽ hoạt động, vì vậy tôi tưởng tượng bạn đã đơn giản hóa nó cho câu hỏi. (Và giữ cho câu hỏi đơn giản và súc tích thường là một điều rất tốt.)

Nhưng trong trường hợp này tôi tưởng tượng một kết quả là một phép tính và một kết quả là một hằng số.

Điều này vi phạm quy tắc chính yếu của lập trình dấu phẩy động: Không bao giờ so sánh bằng.

Những lý do cho điều này là một chút tinh tế 1 nhưng điều quan trọng cần nhớ là chúng thường không hoạt động (ngoại trừ, trớ trêu thay, đối với các giá trị tích phân) và thay thế là một so sánh mờ dọc theo dòng:

if abs(a - y) < epsilon



1. Một trong những vấn đề chính liên quan đến cách chúng ta viết số trong các chương trình. Chúng tôi viết chúng dưới dạng chuỗi thập phân và kết quả là hầu hết các phân số chúng tôi viết không có biểu diễn chính xác của máy. Chúng không có dạng hữu hạn chính xác vì chúng lặp lại ở dạng nhị phân. Mỗi phần máy là một số hữu tỷ có dạng x / 2 n . Bây giờ, các hằng số là số thập phân và mỗi hằng số thập phân là một số hữu tỷ có dạng x / (2 n * 5 m ). Các số 5 m là số lẻ, vì vậy không có yếu tố 2 n cho bất kỳ số nào trong số chúng. Chỉ khi m == 0 mới có biểu diễn hữu hạn trong cả khai triển nhị phân và thập phân của phân số. Vì vậy, 1,25 là chính xác vì đó là 5 / (2 2 * 5 0) nhưng 0,1 không phải vì đó là 1 / (2 0 * 5 1 ). Trong thực tế, trong chuỗi 1.01 .. 1.99 chỉ có 3 trong số các số có thể biểu diễn chính xác: 1.25, 1.50 và 1.75.


DigitalRoss khá khó để hiểu một vài thuật ngữ trong bình luận của bạn, nhưng vâng, nó rất nhiều thông tin. Và tôi sẽ google các điều khoản này. Cảm ơn :)
Santosh Sonarikar

Không đủ an toàn để so sánh trên phao, với điều kiện bạn làm tròn kết quả mỗi lần và nằm trong một vài chữ số có nghĩa? Nói cách khácround($float, 3) == round($other, 3)
Michael Butler

2

Đây là giải pháp để so sánh các dấu phẩy động hoặc số thập phân

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

Truyền một decimalbiến đến stringvà bạn sẽ ổn.


1

So sánh float cho đẳng thức có thuật toán O (n) ngây thơ.

Bạn phải chuyển đổi từng giá trị float thành một chuỗi, sau đó so sánh từng chữ số bắt đầu từ phía bên trái của mỗi biểu diễn chuỗi float bằng các toán tử so sánh số nguyên. PHP sẽ tự động tự động chữ số ở mỗi vị trí chỉ mục thành một số nguyên trước khi so sánh. Chữ số đầu tiên lớn hơn chữ số kia sẽ phá vỡ vòng lặp và khai báo số float mà nó thuộc về số lớn hơn của hai. Trung bình, sẽ có 1/2 * n so sánh. Đối với các phao bằng nhau, sẽ có n so sánh. Đây là trường hợp xấu nhất cho thuật toán. Kịch bản trường hợp tốt nhất là chữ số đầu tiên của mỗi float là khác nhau, chỉ gây ra một so sánh.

Bạn không thể sử dụng INTEGER COMPARISON OPERATORS trên các giá trị float thô với mục đích tạo ra kết quả hữu ích. Kết quả của các hoạt động như vậy không có ý nghĩa gì vì bạn không so sánh các số nguyên. Bạn đang vi phạm tên miền của mỗi nhà khai thác tạo ra kết quả vô nghĩa. Điều này giữ cho so sánh delta là tốt.

Sử dụng các toán tử so sánh số nguyên cho những gì chúng được thiết kế để: so sánh các số nguyên.

GIẢI PHÁP ĐƠN GIẢ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;
  }
 }

?>

1

2019

TL; DR

Sử dụng chức năng của tôi dưới đây, như thế này if(cmpFloats($a, '==', $b)) { ... }

  • Dễ đọc / ghi / thay đổi: cmpFloats($a, '<=', $b)vs.bccomp($a, $b) <= -1
  • Không phụ thuộc cần thiết.
  • Hoạt động với mọi phiên bản PHP.
  • Hoạt động với số âm.
  • Hoạt động với số thập phân dài nhất bạn có thể tưởng tượng.
  • Nhược điểm: Chậm hơn một chút so với bccomp ()

Tóm lược

Tôi sẽ tiết lộ bí ẩn.

$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

Vì vậy, nếu bạn thử dưới đây, nó sẽ bằng nhau:

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

Làm thế nào để có được giá trị thực của float?

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

Làm thế nào bạn có thể so sánh?

  1. Sử dụng các hàm BC Math . (bạn vẫn sẽ nhận được rất nhiều khoảnh khắc wtf-aha-gotcha)
  2. Bạn có thể thử câu trả lời của @ Gladhon, sử dụng PHP_FLOAT_EPSILON (PHP 7.2).
  3. Nếu so sánh số float với ==!=, bạn có thể đánh máy chúng thành chuỗi, nó sẽ hoạt động hoàn hảo:

Nhập cast với chuỗi :

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

Hoặc typecast với 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"

Cảnh báo:

Tránh các giải pháp liên quan đến thao túng phao toán học (nhân, chia, v.v.) rồi so sánh, chủ yếu chúng sẽ giải quyết một số vấn đề và đưa ra các vấn đề khác.


Giải pháp đề xuất

Tôi đã tạo chức năng PHP thuần túy (không cần thư viện / thư viện / tiện ích mở rộng). Kiểm tra và so sánh từng chữ số dưới dạng chuỗi. Cũng hoạt động với số âm.

/**
 * 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)

1

Chức năng từ @evilReiko có một số lỗi như sau:

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

Trong hàm của tôi, tôi đã sửa các lỗi này, nhưng trong một số trường hợp, hàm này trả về các câu trả lời sai:

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

Đã sửa lỗi chức năng để so sánh phao

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

Trả lời câu hỏi của bạn

$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

Đây là một lớp hữu ích từ thư viện cá nhân của tôi để xử lý các số dấu phẩy động. Bạn có thể chỉnh sửa nó theo ý thích của bạn và chèn bất kỳ giải pháp nào bạn thích vào các phương thức lớp :-).

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

0

Câu trả lời đơn giản:

if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.