Vấn đề về PHP7.1 json_encode () Float


93

Đây không phải là một câu hỏi vì nó cần được chú ý nhiều hơn. Tôi đã cập nhật một ứng dụng sử dụng json_encode()lên PHP7.1.1 và tôi đã gặp sự cố khi số float được thay đổi để đôi khi mở rộng ra 17 chữ số. Theo tài liệu, PHP 7.1.x bắt đầu được sử dụng serialize_precisionthay vì độ chính xác khi mã hóa các giá trị kép. Tôi đoán điều này gây ra một giá trị mẫu là

472.185

để trở thành

472.18500000000006

sau khi giá trị đó đi qua json_encode(). Kể từ khi phát hiện ra, tôi đã hoàn nguyên trở lại PHP 7.0.16 và tôi không còn gặp vấn đề gì json_encode()nữa. Tôi cũng đã cố gắng cập nhật lên PHP 7.1.2 trước khi hoàn nguyên về PHP 7.0.16.

Lý do đằng sau câu hỏi này xuất phát từ PHP - Floating Number Precision , tuy nhiên, cuối cùng tất cả lý do cho điều này là do sự thay đổi từ cách sử dụng precision sang serialize_pre precision trong json_encode().

Nếu ai đó biết giải pháp cho vấn đề này, tôi rất sẵn lòng lắng nghe lý do / sửa chữa.

Trích từ mảng đa chiều (trước):

[staticYaxisInfo] => Array
                    (
                        [17] => stdClass Object
                            (
                                [variable_id] => 17
                                [static] => 1
                                [min] => 0
                                [max] => 472.185
                                [locked_static] => 1
                            )

                    )

và sau khi trải qua json_encode()...

"staticYaxisInfo":
            {
                "17":
                {
                    "variable_id": "17",
                    "static": "1",
                    "min": 0,
                    "max": 472.18500000000006,
                    "locked_static": "1"
                }
            },

6
ini_set('serialize_precision', 14); ini_set('precision', 14);có thể sẽ làm cho nó tuần tự hóa như trước đây, tuy nhiên nếu bạn thực sự dựa vào độ chính xác cụ thể trên phao của mình thì bạn đang làm sai.
apokryfos

1
"Nếu có ai biết về giải pháp cho vấn đề này" - vấn đề gì? Tôi không thể thấy bất kỳ vấn đề ở đây. Nếu bạn giải mã JSON bằng PHP, bạn sẽ nhận lại giá trị mà bạn đã mã hóa. Và nếu bạn giải mã nó bằng một ngôn ngữ khác, rất có thể bạn sẽ nhận được cùng một giá trị. Dù bằng cách nào, nếu bạn in giá trị có 12 chữ số, bạn sẽ nhận lại giá trị ban đầu ("đúng"). Bạn có cần độ chính xác nhiều hơn 12 chữ số thập phân cho các phao được ứng dụng của bạn sử dụng không?
axiac

12
@axiac 472.185! = 472.18500000000006. Có sự khác biệt rõ ràng trước và sau, đây là một phần của yêu cầu AJAX đối với trình duyệt và giá trị cần phải ở trạng thái ban đầu.
Gwi7d31

4
Tôi đang cố gắng tránh sử dụng chuyển đổi chuỗi vì sản phẩm cuối cùng là Highcharts và nó sẽ không chấp nhận chuỗi. Tôi nghĩ rằng tôi sẽ coi nó là rất kém hiệu quả và cẩu thả nếu bạn lấy một giá trị float, ép nó thành một chuỗi, gửi nó đi và sau đó yêu cầu javascript diễn giải chuỗi trở lại một float với parseFloat (). Không bạn?
Gwi7d31

1
@axiac Tôi lưu ý rằng bạn là PHP json_decode () mang lại giá trị float ban đầu. Tuy nhiên, khi javascript chuyển chuỗi JSON trở lại một đối tượng, nó không chuyển đổi giá trị trở lại 472.185 như bạn đã nói bóng gió ... do đó có thể xảy ra sự cố. Tôi sẽ gắn bó với những gì tôi đã đi.
Gwi7d31

Câu trả lời:


97

Điều này khiến tôi phát điên lên một chút cho đến khi cuối cùng tôi tìm thấy lỗi này , nó chỉ cho bạn đến RFC này , nó cho biết

Hiện đang json_encode()sử dụng EG (độ chính xác) được đặt thành 14. Điều đó có nghĩa là tối đa 14 chữ số được sử dụng để hiển thị (in) số. IEEE 754 double hỗ trợ độ chính xác cao hơn và serialize()/ var_export()sử dụng PG (serialize_pre precision ) được đặt thành 17 là mặc định để chính xác hơn. Vì json_encode()sử dụng EG (độ chính xác), json_encode()loại bỏ các chữ số thấp hơn của phần phân số và phá hủy giá trị ban đầu ngay cả khi float của PHP có thể giữ giá trị float chính xác hơn.

Và (nhấn mạnh của tôi)

RFC này đề xuất giới thiệu một cài đặt mới EG (precision) = - 1 và PG (serialize_pre precision) = - 1 sử dụng chế độ 0 của zend_dtoa () sử dụng thuật toán tốt hơn để làm tròn số thực (-1 được sử dụng để biểu thị chế độ 0) .

Tóm lại, có một cách mới để làm cho PHP 7.1 json_encodesử dụng công cụ chính xác mới và cải tiến. Trong php.ini, bạn cần thay đổi serialize_precisionthành

serialize_precision = -1

Bạn có thể xác minh nó hoạt động bằng dòng lệnh này

php -r '$price = ["price" => round("45.99", 2)]; echo json_encode($price);'

Bạn sẽ nhận được

{"price":45.99}

G(precision)=-1PG(serialize_precision)=-1 cũng có thể được sử dụng trong PHP 5.4
kittygirl

1
Hãy cẩn thận với serialize_precision = -1. Với -1, mã này sẽ echo json_encode([528.56 * 100]);in[52855.99999999999]
vl.lapikov,

3
@ vl.lapikov Tuy nhiên, điều đó nghe giống như một lỗi dấu chấm động chung . Đây là bản demo , nơi bạn có thể thấy rõ ràng nó không chỉ là json_encodevấn đề
Machavity

39

Là nhà phát triển plugin, tôi không có quyền truy cập chung vào cài đặt php.ini của máy chủ. Vì vậy, dựa trên câu trả lời của Machavity, tôi đã viết đoạn mã nhỏ này mà bạn có thể sử dụng trong tập lệnh PHP của mình. Chỉ cần đặt nó lên đầu tập lệnh và json_encode sẽ tiếp tục hoạt động như bình thường.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'serialize_precision', -1 );
}

Trong một số trường hợp, cần phải đặt thêm một biến nữa. Tôi đang thêm giải pháp này làm giải pháp thứ hai vì tôi không chắc liệu giải pháp thứ hai có hoạt động tốt hay không trong mọi trường hợp khi giải pháp đầu tiên đã được chứng minh là hoạt động.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

3
Hãy cẩn thận với điều đó, vì plugin của bạn có thể thay đổi cài đặt không mong muốn cho phần còn lại của ứng dụng dành cho nhà phát triển. Nhưng, IMO, tôi không chắc chắn như thế nào phá hoại tùy chọn này có thể nhận được ... lol
igorsantos07

Lưu ý rằng thay đổi giá trị chính xác (ví dụ thứ hai) có thể có tác động lớn hơn trong các phép toán khác mà bạn có ở đó. php.net/manual/en/ini.core.php#ini.pre precision
Ricardo Martins

@RicardoMartins: Theo tài liệu, độ chính xác mặc định là 14. Sửa lỗi ở trên tăng con số này lên 17. Vì vậy, nó phải chính xác hơn nữa. Bạn có đồng ý không?
alev

@alev những gì tôi đang nói là chỉ cần thay đổi serialize_pre precision là đủ và không ảnh hưởng đến các hành vi PHP khác mà ứng dụng của bạn có thể gặp phải
Ricardo Martins

4

Tôi đang mã hóa các giá trị tiền tệ và có những thứ như 330.46mã hóa 330.4600000000000363797880709171295166015625. Nếu bạn không muốn hoặc không thể, hãy thay đổi cài đặt PHP và bạn biết trước cấu trúc của dữ liệu, có một giải pháp rất đơn giản phù hợp với tôi. Chỉ cần truyền nó thành một chuỗi (cả hai thao tác sau đều thực hiện tương tự):

$data['discount'] = (string) $data['discount'];
$data['discount'] = '' . $data['discount'];

Đối với trường hợp sử dụng của tôi, đây là một giải pháp nhanh chóng và hiệu quả. Chỉ cần lưu ý rằng điều này có nghĩa là khi bạn giải mã nó trở lại từ JSON, nó sẽ là một chuỗi vì nó sẽ được đặt trong dấu ngoặc kép.


4

Tôi đã giải quyết vấn đề này bằng cách đặt cả precision và serialize_pre khít thành cùng một giá trị (10):

ini_set('precision', 10);
ini_set('serialize_precision', 10);

Bạn cũng có thể đặt điều này trong php.ini của mình


3

Tôi đã gặp vấn đề tương tự nhưng chỉ serialize_pre precision = -1 không giải quyết được vấn đề. Tôi phải thực hiện thêm một bước nữa, để cập nhật giá trị của độ chính xác từ 14 lên 17 (vì nó được đặt trên tệp ini PHP7.0 của tôi). Rõ ràng, việc thay đổi giá trị của số đó sẽ thay đổi giá trị của float được tính toán.


3

Các giải pháp khác không hiệu quả với tôi. Đây là những gì tôi phải thêm vào khi bắt đầu thực thi mã của mình:

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

Về cơ bản đây không phải là câu trả lời của Alin Pop sao?
igorsantos07

1

Đối với tôi, vấn đề là khi JSON_NUMERIC_CHECK như đối số thứ hai của json_encode () được truyền, truyền tất cả các số kiểu thành int (không chỉ số nguyên)


1

Lưu trữ nó dưới dạng một chuỗi với độ chính xác chính xác mà bạn cần bằng cách sử dụng number_format, sau json_encodeđó sử dụng JSON_NUMERIC_CHECKtùy chọn:

$foo = array('max' => number_format(472.185, 3, '.', ''));
print_r(json_encode($foo, JSON_NUMERIC_CHECK));

Bạn lấy:

{"max": 472.185}

Lưu ý rằng điều này sẽ khiến TẤT CẢ các chuỗi số trong đối tượng nguồn của bạn được mã hóa thành các số trong JSON kết quả.


1
Tôi đã thử nghiệm điều này trong PHP 7.3 và nó không hoạt động (đầu ra vẫn có độ chính xác quá cao). Rõ ràng cờ JSON_NUMERIC_CHECK bị hỏng vì PHP 7.1 - php.net/manual/de/json.constants.php#123167
Philipp

0
$val1 = 5.5;
$val2 = (1.055 - 1) * 100;
$val3 = (float)(string) ((1.055 - 1) * 100);
var_dump(json_encode(['val1' => $val1, 'val2' => $val2, 'val3' => $val3]));
{
  "val1": 5.5,
  "val2": 5.499999999999994,
  "val3": 5.5
}

0

Có vẻ như sự cố xảy ra khi serializeserialize_precisionđược đặt thành các giá trị khác nhau. Trong trường hợp của tôi là 14 và 17 tương ứng. Đặt cả hai thành 14 đã giải quyết được vấn đề, cũng như đặt serialize_precisionthành -1.

Giá trị mặc định của serialize_precision đã được thay đổi thành -1 kể từ PHP 7.1.0 , có nghĩa là "một thuật toán nâng cao để làm tròn các số như vậy sẽ được sử dụng". Nhưng nếu bạn vẫn gặp sự cố này, có thể là do bạn có tệp cấu hình PHP từ phiên bản trước. (Có thể bạn đã giữ tệp cấu hình của mình khi nâng cấp?)

Một điều khác cần xem xét là việc sử dụng giá trị float trong trường hợp của bạn có hợp lý hay không. Có thể có hoặc có thể không hợp lý khi sử dụng các giá trị chuỗi chứa các số của bạn để đảm bảo số lượng vị trí thập phân thích hợp luôn được giữ lại trong JSON của bạn.


-1

Bạn có thể thay đổi [max] => 472.185 từ float thành một chuỗi ([max] => '472.185') trước json_encode (). Vì json cũng là một chuỗi, nên việc chuyển đổi các giá trị float của bạn thành chuỗi trước json_encode () sẽ duy trì giá trị mà bạn mong muốn.


Điều này đúng về mặt kỹ thuật ở một mức độ nhất định, nhưng rất kém hiệu quả. Nếu một Int / Float trong một chuỗi JSON không được trích dẫn, thì Javascript có thể xem nó như một Int / Float thực sự. Việc thực hiện kết xuất của bạn buộc bạn phải chuyển mọi giá trị trở lại Int / Float một lần ở phía trình duyệt. Tôi thường xử lý hơn 10000 giá trị khi làm việc trên dự án này theo yêu cầu. Nhiều quá trình xử lý cồng kềnh sẽ xảy ra.
Gwi7d31

Nếu bạn đang sử dụng JSON để gửi dữ liệu ở đâu đó và một số được mong đợi nhưng bạn gửi một chuỗi, điều đó không được đảm bảo sẽ hoạt động. Trong các tình huống mà nhà phát triển ứng dụng gửi không có quyền kiểm soát ứng dụng nhận, thì đây không phải là một giải pháp.
osullic
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.