PHP7.1 json_encode () Float Sorunu


93

Bu bir soru değil, çünkü daha çok farkında olmak. json_encode()PHP7.1.1'i kullanan bir uygulamayı güncelledim ve kayan değerlerin bazen 17 haneyi uzatacak şekilde değiştirilmesiyle ilgili bir sorun görüyordum. Belgelere göre PHP 7.1.x, serialize_precisionçift ​​değerleri kodlarken kesinlik yerine kullanılmaya başlandı . Bunun örnek bir değer yarattığını tahmin ediyorum

472.185

olmak

472.18500000000006

bu değer geçtikten sonra json_encode(). Keşfimden bu yana, PHP 7.0.16'ya geri döndüm ve artık sorunum yok json_encode(). Ayrıca PHP 7.0.16'ya geri dönmeden önce PHP 7.1.2'ye güncellemeyi denedim.

Bu sorunun arkasındaki mantık PHP - Kayan Sayı Hassasiyetinden kaynaklanmaktadır , ancak bunun son nedeni, hassasiyetten serialize_precision kullanımına geçiştir json_encode().

Herhangi biri bu soruna bir çözüm biliyorsa, gerekçeyi / düzeltmeyi dinlemekten çok mutlu olurum.

Çok boyutlu diziden alıntı (önce):

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

                    )

ve geçtikten sonra 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);muhtemelen eskisi gibi seri hale getirecektir, ancak gerçekten kayan noktalarda belirli bir hassasiyete güveniyorsanız, yanlış bir şeyler yapıyorsunuz demektir.
apokryfos

1
"Bu soruna bir çözüm bilen varsa" - hangi sorun? Burada herhangi bir sorun göremiyorum. JSON kodunu PHP kullanarak çözerseniz, kodladığınız değeri geri alırsınız. Ve farklı bir dil kullanarak kodunu çözerseniz, büyük olasılıkla aynı değeri elde edersiniz. Her iki durumda da değeri 12 basamaklı yazdırırsanız, orijinal ("doğru") değeri geri alırsınız. Uygulamanız tarafından kullanılan kayan sayılar için 12'den fazla ondalık basamak hassasiyetine mi ihtiyacınız var?
axiac

12
@axiac 472.185! = 472.18500000000006. Öncesinde ve sonrasında net bir fark vardır: Bu, bir tarayıcıya yapılan AJAX isteğinin bir parçasıdır ve değerin orijinal durumunda kalması gerekir.
Gwi7d31

4
Son ürün Highcharts olduğundan ve dizeleri kabul etmeyeceğinden dize dönüşümü kullanmaktan kaçınmaya çalışıyorum. Bir kayan değer alırsanız, onu bir dize olarak atarsanız, gönderirseniz ve ardından javascript'in dizeyi parseFloat () ile bir şamandıraya geri döndürmesini sağlarsanız, bunun çok verimsiz ve özensiz olduğunu düşünüyorum. Değil mi
Gwi7d31

1
@axiac PHP olduğunuzu not ediyorum json_decode () orijinal float değerini geri getiriyor. Bununla birlikte, javascript JSON dizesini bir nesneye döndürdüğünde, potansiyel olarak ima ettiğiniz gibi değeri 472.185'e geri dönüştürmez ... Yaptığım şeye bağlı kalacağım.
Gwi7d31

Yanıtlar:


97

Sonunda buldum kadar bu biraz beni deli sürdü bu hatayı edinebileceğiniz bu RFC diyor

Halihazırda json_encode(), 14'e ayarlanmış olan EG (kesinlik) kullanmaktadır. Bu, numarayı görüntülemek (yazdırmak) için en fazla 14 rakamın kullanıldığı anlamına gelir. IEEE 754 double, daha yüksek hassasiyeti destekler ve serialize()/ daha hassas var_export()olması için varsayılan olarak 17'ye ayarlanan PG'yi (serialize_precision) kullanır. İtibaren json_encode()kullanımları EG (hassas), json_encode()fraksiyon parça alt basamak kaldırır ve PHP yüzer daha kesin yüzer değer de taşıyabilir bile orijinal değeri yok eder.

Ve (vurgu benim)

Bu RFC, kayan sayıları yuvarlamak için daha iyi algorigthm kullanan zend_dtoa () 'nın 0 modunu kullanan yeni bir EG (kesinlik) = - 1 ve PG (serialize_precision) = - 1 ayarını sunmayı önerir (-1, 0 modunu belirtmek için kullanılır) .

Kısacası, PHP 7.1'in json_encodeyeni ve geliştirilmiş hassaslık motorunu kullanmasını sağlamanın yeni bir yolu var . Gelen php.ini Eğer değişime ihtiyaç serialize_precisioniçin

serialize_precision = -1

Bu komut satırı ile çalıştığını doğrulayabilirsiniz

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

Almalısın

{"price":45.99}

G(precision)=-1ve PG(serialize_precision)=-1 ayrıca PHP 5.4'te de kullanılabilir
kittygirl

1
Dikkatli olun serialize_precision = -1. -1 ile bu kod echo json_encode([528.56 * 100]);yazdırılır[52855.99999999999]
vl.lapikov

3
@ vl.lapikov Bu daha çok genel bir kayan nokta hatası gibi geliyor . İşte bunun sadece bir sorun olmadığını açıkça görebileceğiniz bir demojson_encode
Machavity

39

Bir eklenti geliştiricisi olarak, bir sunucunun php.ini ayarlarına genel erişimim yok. Machavity'nin cevabına dayanarak, PHP betiğinizde kullanabileceğiniz bu küçük kod parçasını yazdım. Basitçe betiğin üstüne koyun ve json_encode her zamanki gibi çalışmaya devam edecektir.

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

Bazı durumlarda bir değişken daha ayarlamak gerekir. Bunu ikinci bir çözüm olarak ekliyorum çünkü ikinci çözümün ilk çözümün işe yaradığını kanıtladığı her durumda iyi çalışıp çalışmadığından emin değilim.

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

3
Eklentiniz geliştirici uygulamasının geri kalanı için beklenmedik ayarları değiştirebileceğinden buna dikkat edin. Ama IMO, bu seçeneğin ne kadar yıkıcı olacağından emin değilim ... lol
igorsantos07

Kesinlik değerini değiştirmenin (ikinci örnek), sahip olduğunuz diğer matematiksel işlemlerde daha büyük bir etkisi olabileceğini unutmayın. php.net/manual/en/ini.core.php#ini.precision
Ricardo Martins

@RicardoMartins: Belgelere göre varsayılan hassasiyet 14'tür. Yukarıdaki düzeltme bunu 17'ye çıkarır. Bu yüzden daha da kesin olmalıdır. Katılıyor musun?
alev

@alev, sadece serialize_precision'ı değiştirmenin yeterli olduğunu ve uygulamanızın yaşayabileceği diğer PHP davranışlarından ödün vermediğinizi söylüyordum
Ricardo Martins

4

Parasal değerleri kodluyordum ve 330.46kodlama gibi şeylerim vardı 330.4600000000000363797880709171295166015625. PHP ayarlarını değiştirmek istemiyorsanız veya yapamıyorsanız ve verilerin yapısını önceden biliyorsanız, benim için çalışan çok basit bir çözüm var. Basitçe bir dizeye çevirin (aşağıdakilerin ikisi de aynı şeyi yapar):

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

Benim kullanım durumum için bu hızlı ve etkili bir çözümdü. Bunun, JSON'dan kodunu çözdüğünüzde çift tırnak içine alınacağı için bir dize olacağı anlamına geldiğini unutmayın.


4

Bunu hem hassasiyeti hem de serialize_precision'ı aynı değere ayarlayarak çözdüm (10):

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

Bunu php.ini'nizde de ayarlayabilirsiniz.


3

Aynı sorunu yaşadım ama sadece serialize_precision = -1 sorunu çözmedi. Kesinliğin değerini 14'ten 17'ye güncellemek için bir adım daha atmam gerekti (PHP7.0 ini dosyamda ayarlandığı gibi). Görünüşe göre, bu sayının değerini değiştirmek, hesaplanan kayan nokta değerini değiştiriyor.


3

Diğer çözümler benim için işe yaramadı. İşte kod yürütmemin başlangıcında eklemem gerekenler:

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

Bu temelde Alin Pop'un cevabıyla aynı değil mi?
igorsantos07

1

Bana gelince, sorun, JSON_NUMERIC_CHECK'in json_encode () 'un ikinci argümanı olarak geçtiği zamandı, bu tüm sayıları int türüne çeviriyor (sadece tamsayı değil)


1

Bunu kullanarak tam olarak ihtiyacınız olan hassasiyette bir dize olarak saklayın number_format, ardından seçeneği json_encodekullanarak JSON_NUMERIC_CHECK:

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

Sen alırsın:

{"max": 472.185}

Bunun, kaynak nesnenizdeki TÜM sayısal dizelerin elde edilen JSON'da sayı olarak kodlanmasını sağlayacağını unutmayın.


1
Bunu PHP 7.3'te test ettim ve çalışmıyor (çıktı hala çok yüksek hassasiyete sahip). Görünüşe göre JSON_NUMERIC_CHECK bayrağı PHP 7.1'den beri kırılmış - 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

Sorun, farklı değerlere ayarlandığında serializeve serialize_precisionayarlandığında ortaya çıkıyor gibi görünüyor . Benim durumumda sırasıyla 14 ve 17. İkisini de 14 olarak ayarlamak, ayarlarda olduğu gibi sorunu çözdüserialize_precision -1 .

serialize_precision PHP 7.1.0'dan itibaren varsayılan değeri -1 olarak değiştirildi, bu da "bu tür sayıları yuvarlamak için gelişmiş bir algoritma kullanılacak" anlamına geliyor. Ancak hala bu sorunu yaşıyorsanız, bunun nedeni önceki bir sürümden bir PHP yapılandırma dosyanızın olması olabilir. (Yükseltme sırasında yapılandırma dosyanızı saklamış olabilirsiniz?)

Dikkate alınması gereken başka bir şey, sizin durumunuzda kayan değerlerin kullanılmasının mantıklı olup olmadığıdır. JSON'nuzda her zaman uygun ondalık basamak sayısının korunmasını sağlamak için sayılarınızı içeren dize değerleri kullanmak mantıklı olabilir veya olmayabilir.


-1

Json_encode () 'dan önce [max] => 472.185 değerini floattan dizgeye ([max] =>' 472.185 ') değiştirebilirsiniz. Json zaten bir dizge olduğundan, float değerlerinizi json_encode () 'dan önce dizelere dönüştürmek istediğiniz değeri koruyacaktır.


Bu teknik olarak bir dereceye kadar doğrudur, ancak çok verimsizdir. JSON dizesindeki bir Int / Float tırnak içine alınmamışsa, Javascript bunu gerçek bir Int / Float olarak görebilir. Yorumunuzu gerçekleştirmek sizi her bir değeri tarayıcı tarafında bir kez Int / Float'a geri çevirmeye zorlar. İstek başına bu proje üzerinde çalışırken sık sık 10000+ değerle uğraşıyordum. Sonunda çok fazla şişkinlik işleme gerçekleşebilirdi.
Gwi7d31

Bir yere veri göndermek için JSON kullanıyorsanız ve bir sayı bekleniyorsa ancak bir dize gönderirseniz, bunun çalışması garanti edilmez. Gönderen uygulamanın geliştiricisinin alıcı uygulama üzerinde kontrol sahibi olmadığı durumlarda, bu bir çözüm değildir.
osullic
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.