Soru Kayan nokta sorunu için PHP php.ini hassas çözümüne güvenebilir miyim?


İçin bazı geçici çözümler buldum kayan nokta problemi PHP'de:

php.ini ayarı precision = 14

342349.23 - 341765.07 = 584.15999999992 // floating point problem

php.ini ayarı, diyelim precision = 8 

342349.23 - 341765.07 = 584.16 // voila!

Demo: http://codepad.org/r7o086sS

Bu ne kadar kötü?

1. Sadece 2 basamaklı hesaplamaya (paraya) ihtiyacım varsa bu çözüme güvenebilir miyim?

2. Bu çözümler başarısız olduğunda bana net bir örnek sunamaz mısınız? 

Düzenleme: 3. Hangi php.ini.precision değeri en iyi iki basamağa uygundur, para hesaplamaları 


  • Tamsayı hesaplamalarını kullanamayacağımı lütfen unutmayın (float * 100 = sent), bunun için çok geç.
  • 10 ^ 6'dan daha yüksek sayılarda çalışmayacağım.
  • Sayıları karşılaştırmam gerekmiyor

GÜNCELLEŞTİRME

@Baba cevabı iyidir, ama kullandı precision=20, precision=6 Onun testlerinde ... Yani hala işe yarayıp yaramadığından emin değilim.

Lütfen aşağıdakileri dikkate alınız:

Diyelimki precision = 8 ve yaptığım tek şey ek + ve çıkarma -

A + B = C

A - B = C

Soru 1: Hassas çözüm, A ve B'nin ondalık basamaklı bir sayı olduğu 0,999999,99 arasındaki sayılar için başarısız olur mu? Eğer öyleyse lütfen bana bir örnek verin.

Basit test işi yapardı:

// if it fails what if I use 9,10,11 ???
// **how to find when it fails??? **
ini_set('precision', 8); 
for($a=0;$a<999999.99;$a+=0.01) {
  for($b=0;$b<999999.99;$b+=0.01) {
     // mind I don't need to test comparision (round($a-$b,2) == ($a-$b))
     echo ($a + $b).','.($a - $b)." vs ";
     echo round($a + $b, 2).','.round($a - $b, 2)."\n";
  }
}

ama belli ki 99999999 * 2 çok büyük bir iş bu yüzden bu testi yapamam

Soru 2: Hassas geçici çözüm başarısız olduğunda nasıl hesaplanır / hesaplanır? Böyle çılgın testler olmadan mı? Bunun için herhangi bir matematiksel *, doğru cevap var mı? Hesaplama nasıl başarısız olacak ya da olmayacak?

* kayan nokta hesaplamalarını bilmemize gerek yok, ancak kesinliği ve A ve B aralığını biliyorsanız, geçici çözüm başarısız olduğunda


Lütfen sorun Gerçekten biliyorum sent ve bcmath en iyi çözümdür. Ama yine de emin değilim çünkü geçici bir çözüm ve eklenme için geçici bir çözümün başarısız olacağından emin değilim.


27
2018-02-01 22:43


Menşei


Eğer 2 basamaklı (para) biriyle uğraşıyorsanız, neden tamsayıları kullanmıyorsunuz ve son sonucu 100'e böler misiniz? - Wiseguy
@Wiseguy Sorumu düzenledim - Peter
@PLAudet Bir apache vhost ile ayarlanabilir php_value Örneğin... - Michael Berkowski
sadece kullanma hakkında ne round($value,2) veya number_format($value,2) - Pitchinnate
Uygulamanızın herhangi bir bölümü varsa tam sayıları kullanmak yardımcı olmaz. (Toplu olarak satın alınan öğeler için yüzdeye dayalı fiyat indirimleri veya öğe başına maliyet hesaplaması gibi) - Alex Howansky


Cevaplar:


Giriş

Kayan nokta aritmetiği birçok insan tarafından ezoterik bir konu olarak kabul edilir. Bu oldukça şaşırtıcıdır çünkü kayan nokta bilgisayar sistemlerinde her yerde mevcuttur. Kesirli sayıların çoğunun ikili kesir olarak tam bir temsili yoktur, bu nedenle bazı yuvarlamalar yaşanır. İyi bir başlangıç Her Bilgisayar Bilimcinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler

Sorular

Soru 1

Sadece ihtiyacım olursa bu çözüme güvenebilir miyim kesin 2 basamaklı hesaplamalar (para)?

cevap 1

İhtiyacınız varsa hassas 2 basamak  o zaman cevap YOK HAYIR  Bunu anlamak için php hassas ayarlarını kullanamazsınız. 2 haneli siz bile olsanız bile ondalık not going to work on numbers higher than 10^6.

Hesaplamalar sırasında uzunluk 8'den küçükse hassas uzunluğun arttırılabileceği olasılığı vardır.

soru 2

Bu çözümler başarısız olduğunda bana net bir örnek sunamaz mısınız?

Cevap 2

ini_set('precision', 8); // your precision
$a =  5.88 ; // cost of 1kg
$q = 2.49 ;// User buys 2.49 kg
$b = $a * 0.01 ; // 10% Discount only on first kg ;
echo ($a * $q) - $b;

Çıktı

14.5824 <---- not precise 2 digits calculations even if precision is 8

Soru 3

Hangi php.ini.precision değeri en iyi iki basamağa, para hesaplarına uyuyor?

Cevap 3

Kesinlik ve Para hesaplaması 2 farklı şeydir ... Finansal hesaplarınız veya kayan nokta uzunluğunuz için PHP hassaslığını kullanmak için iyi bir fikir değildir.

Basit test

Lest Birlikte kullanarak bazı örnek çalıştırın bcmath , number_format ve basit minus

Base 

$a = 342349.23;
$b = 341765.07;

Example A

ini_set('precision', 20); // set to 20 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Çıktı

584.15999999997438863
584.15999999999996817    <----- Round having a party 
584.16
584.15  <-------- here is 15 because precision value is 20

Example B

ini_set('precision', 14); // change to  14 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Çıktı

584.15999999997
584.16
584.16
584.16  <-------- at 14 it changed to 16

Example C

ini_set('precision', 6); // change to  6 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Çıktı

584.16
584.16
584.16
584.00  <--- at 6 it changed to 00 

Example D

ini_set('precision', 3); // change to 3
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Çıktı

584
584
584.16   <-------------------------------- They only consistent value 
0.00  <--- at 3 .. everything is gone 

Sonuç

Kayan nokta unutun ve sadece hesaplayın cents daha sonra bölü 100 eğer bu çok geç ise sadece number_format bana uygun görünüyor.

Güncelleştirme

Soru 1: A ve B'nin ondalık basamaklı bir sayı olduğu 0..999999,99 arasındaki sayılar için kesin çözüm geçici olarak başarısız olur mu? Öyleyse lütfen bana bir örnek verin

Form 0 için 999999.99 artışında 0.01 hakkında 99,999,999 Döngününüzün kombinasyon olasılığı 9,999,999,800,000,000  Gerçekten kimsenin bu testi senin için yürütmek isteyeceğini düşünmüyorum.

Kayan nokta sonlu hassaslıkla ayarlamaya çalışan ikili sayılar olduğundan precision doğruluk sağlamak için sınırlı bir etkisi olurdu. İşte basit bir test:

ini_set('precision', 8);

$a = 0.19;
$b = 0.16;
$c = 0.01;
$d = 0.01;
$e = 0.01;
$f = 0.01;
$g = 0.01;

$h = $a + $b + $c + $d + $e + $f + $g;

echo "Total: " , $h , PHP_EOL;


$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;

echo $i , PHP_EOL;

Çıktı

Total: 0.4
1.0408341E-17     <--- am sure you would expect 0.00 here ;

Deneyin

echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;

Çıktı

0
0.00    <------ still confirms number_format is most accurate to maintain 2 digit 

Soru 2: Hassas geçici çözüm başarısız olduğunda nasıl hesaplanır / hesaplanır? Böyle çılgın testler olmadan mı? Bunun için matematiksel bir cevap var mı? Hesaplama nasıl başarısız olacak ya da olmayacak?

Gerçek eşik kalır Kayan nokta var Doğruluk sorunları ama matematiksel çözümler için bakabilirsiniz

kayan nokta hesaplamalarını bilmemize gerek yok, ancak kesinliği ve A ve B aralığını biliyorsanız, geçici çözüm başarısız olduğunda

enter image description here

Bu ifadenin ne anlama geldiğinden emin değilim :)


39
2018-01-29 17:15



Güzel, teşekkürler. Ama ilgili Cevap 2 - çözüm 2 basamaklı sayı ekleme / çıkarma hesaplamaları gibi başarısız olduğunda hala hiçbir örnek yoktur 342349.23 - 341765.07 = 584.15999999992. Ve Örnek C Kesin çözüm acctionaly olduğunu .. doğru işi yapıyor :) - Peter
Ve hala görünüyor precision=8 benim için iyi chocie - Peter
Belgelerim, tam olarak 2 haneye ihtiyacınız var 584.15999999992 öyle görünmüyor - Baba
Bu cevap çok fazla çaba gösteriyor (bunun için sana hayranlarım). Ancak, okuduktan sonra PHP'nin precision yönerge bir şekilde matematiksel hesaplamaları etkiler ve bu sadece en iyi şekilde yanıltıcıdır, çünkü bu sadece dizge dönüştürme için bir parametredir. Bir numaranız varsa ve dizgeye dönüştürüyorsanız önce onunla matematik yaparsın precision davranabilir) muhtemelen kodunuzun mantığını değiştirerek başlamalıdır. - Álvaro González
Çok teşekkür ederim. Gerçekten harika bir cevap. Ve ben tam olarak +500 tuşunu tıklamaktan çok mutluyum, çünkü tam da aradığım şey buydu. Şimdilik, kesin bir çözümün olası bir senaryoda bir seçenek olmadığını tek bir şüphe yok. Tekrar teşekkürler. - Peter


Ben sadece alıntı bu soruna ilginç bir site. (İtibar beklenmiyor :) ama belirtilmelidir:

Bu (kayan nokta) sorununu önlemek için ne yapabilirim?

Bu, ne tür hesaplamalar yaptığınıza bağlı.

  • Özellikle tam olarak eklemek için sonuçlarınıza gerçekten ihtiyacınız varsa, özellikle de   para: özel bir ondalık veri türü kullanın.

  • Tüm bu ondalık ondalık basamakları görmek istemiyorsanız: yalnızca, sonuçlarınızı görüntülerken sabit bir ondalık basamağa yuvarlanır.

  • Ondalık veri türü yoksa, bir alternatif tamsayılarla çalışmaktır, örn. Tamamen para cinsinden para hesaplamaları yapmak. Ama bu daha çok iş ve bazı dezavantajları var.

Sitede ayrıca bazı temel ipuçları PHP

Tamsayıları kullanırım veya özel bir şey yaratırdım Decimal bunun için yazın.

Kullanmaya karar verirseniz BCMath: Bu değerleri, SQL sorgularına veya diğer harici programlara iletirseniz dikkatli olun. Hassasiyetin farkında değilse, istenmeyen yan etkilere yol açabilir. (Muhtemel olan)


4
2018-01-29 16:24



Evet, haklılar. bcmath en iyi seçenek. onu buldum yuvarlama çok beklenmedik sonuçlar üretiyor ve hata yapmak çok kolay. Yuvarlama, yalnızca verileri görüntülemek istiyorsanız tamamdır. - Peter
Özrünü oku. Bazı hesaplamalar yapmadan, sözde örneklerin bilgisayarlarla mümkün olup olmadığını söyleyemem. 0.1349999999 olası bir kayan nokta değeri ise söz konusu. - hek2mgl
Ve yuvarlak böcekler her yerde ortaya çıkıyor ve hata ayıklamak için bir dehşet. - Peter
Tamam sanırım yine haklısın. Para ile çalıştığım her bir php projesinde her basit denklem için yuvarlak kullanmalıyım - Peter
@PeterSzymkowski Btw, kaç farklı kod yapıştırma siteleri paralel olarak kullanıyorsunuz? :) Biliyor musunuz? phpfiddle.org ve ilgili? Paralel olarak kullanılan sitelerin sayısı daha fazla büyüyebilir mi? :) - hek2mgl


Dokümanlara göre hassas yönerge sadece dizelere sayı atarken gösterilen rakamları değiştirir:

hassas  integer
      Kayan noktalı sayılardaki anlamlı basamak sayısı.

Yani bu temelde çok kıvrımlı bir alternatif Number_format () veya money_format ()Ancak, daha az biçimlendirme seçeneğine sahip olması ve farkında olmadığınız başka bazı yan etkilerden muzdarip olabilir:

<?php

$_POST['amount'] = '1234567.89';

$amount = floatval($_POST['amount']);
var_dump($amount);

ini_set('precision', 5);
$amount = floatval($_POST['amount']);
var_dump($amount);

...

float(1234567.89)
float(1.2346E+6)

Düzenle:

Ben ısrar ediyorum: Bu ayar PHP'nin matematiksel hesaplamaları sayılarla değiştirmesini değiştirmez. Bu sadece format seçeneklerini değiştirmek için sihirli bir yol dönüştürme kayan nokta sayılarından (tam sayı değil!) dizelerden. Örnek:

<?php

ini_set('precision', 2);

$amount = 1000;
$price = 98.76;
$total = $amount*$price;

var_dump($amount, $total);

ini_set('precision', 15);
var_dump($amount, $total);

... baskılar:

int(1000)
float(9.9E+4)
int(1000)
float(98760)

Bunu gösteren:

  1. Kayan nokta hesaplamaları etkilenmez, sadece Görüntüle değişiklikler
  2. Integörler her durumda etkilenmez

3
2018-01-29 16:37



Yani 1234567.89 "precision = 5" için oldukça iyi mi? Ya ben hassas kullanırsam 10? (benim sorumu düzenledim) Hangi php.ini.precision değeri iki basamaklı hesaplamalar için en uygun? Sadece hassas changes the digits shown when casting numbers FP sorunu neden kayboldu? - Peter
codepad.org/r7o086sS 10 ^ 13'e kadar olan numaralar için sorun değil mi? - Peter
@PeterSzymkowski - Bütün sorularınızı anladığımdan emin değilim. Kesinlik, sadece ondalık sayıları değil, tüm rakamları dikkate alır. Ve senin FP sorununun "kaybolur" çünkü fark küçük ve yuvarlama ile gitti. Sadece çok karmaşık bir şekilde yuvarlanıyorsunuz. - Álvaro González
Diyelim ki ben 10 = hassasiyet kullanıyorum. Yani sayılar arasındaki herhangi bir alt çıkarma 1..10^10-2 FP Problemi olmadan bana iyi sonuçlar verecek? - Peter
Unutmanı öneririm precision. Tam kontrol sağlayan ve boyut efektleri olmayan birçok fonksiyona sahipsiniz. - Álvaro González


Eğer sonucunuzu basitçe yuvarlarsanız, o zaman bu sizin için kayan nokta sorununuza dikkatinizi dağıtır.

round(342349.23 - 341765.07, 2) = 584.16

1



lütfen sorumu dikkatle okuyun ve yorumlar - Peter


Eğer duyarlık = 8 kullanırsanız, 8 basamaklı bir sayı kullanırsanız 8. rakamdan emin olamazsınız. Bu, 9. basamağı yuvarlamaktan 1 olabilir.

Örneğin

12345678.1 -> 12345678
12345678.9 -> 12345679

Bu çok kötü görünmeyebilir, ancak düşünün

   (11111111.2 + 11111111.2) + 11111111.4
-> (11111111)                + 11111111.4
-> 22222222.4
-> 22222222

Oysa eğer, = 9 kullanırken, bu olurdu 22222222.8 hangi yuvarlak olurdu 22222223.

Sadece eklemeler ve çıkarmalar yapıyorsanız, bu tür hesaplamalarda yuvarlamadan kaçınmanız için gerekenden en az 2 veya daha fazla hassasiyet değeri kullanmalısınız. Çarpma veya bölme yapıyorsanız, daha fazlasına ihtiyacınız olabilir. Gerekli olan minimum minimum değeri burada ve burada kaybolan hanelere yol açabilir.

Yani, sorunuzu cevaplamak için, eğer şanslıysanız ve php hesaplamalarda yüksek hassasiyette kullanırsanız ve daha sonra sonucu daha düşük bir hassasiyetle saklarsa (ve sonra o numarayı kullanıp yapmazsanız diğer hesaplamalar), ancak genel olarak, çok kötü bir fikir çünkü (en azından) hesaplamanızdaki son rakam tamamen güvenilmezdir.


0



Evet. Bu yüzden 10 ^ 6'ya kadar rakamlar sordum ve 8,9,10,11,12 doğruluktan ne haberim? - Peter