JavaScript'te kayan nokta sayısı hassasiyeti ile nasıl başa çıkılır?


618

Aşağıdaki kukla test komut dosyası var:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

Bu, 0.020000000000000004yalnızca yazdırılması gereken sonucu yazdırır 0.02(hesap makinenizi kullanırsanız). Anladığım kadarıyla bu kayan nokta çarpma hassasiyetindeki hatalardan kaynaklanıyor.

Böyle bir durumda doğru sonucu elde edebilmek için iyi bir çözümü olan var mı 0.02? toFixedYuvarlama veya yuvarlama gibi başka bir olasılık olacağını biliyorum , ama tüm sayıyı herhangi bir kesim ve yuvarlama olmadan gerçekten yazdırmak istiyorum. İçinizden birinin güzel, zarif bir çözümü olup olmadığını bilmek istedim.

Tabii ki, aksi takdirde yaklaşık 10 basamağa yuvarlanırım.


118
Aslında hata, 0.1sınırlı bir ikili kayan nokta numarasıyla eşleştirmenin bir yolu olmamasıdır .
Aaron Digulla

10
Kesirlerin çoğu kesin bir hassasiyetle ondalığa dönüştürülemez. İyi bir açıklama burada: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg


53
@SalmanA: JavaScript çalışma zamanınızın bu sorunu sizden gizlemesi yanlış olduğum anlamına gelmez.
Aaron Digulla

5
Aaron'a katılmıyorum, 0.1'i mükemmel ve tamamen ikili olarak kodlamanın yolları var. Ancak IEEE 754 bunu mutlaka tanımlamaz. Bir yandan tamsayı kısmını bir yandan ikili, diğer yandan ondalık kısmı, en fazla n ondalığa kadar, ikili olarak da, normal bir tamsayı> 0 gibi kodlayacağınızı ve son olarak da ondalık noktanın konumunu . Eh, hatasız 0.1'i mükemmel temsil edersiniz. Btw, JS dahili olarak sınırlı sayıda ondalık sayı kullandığından, devs de son ondalıklarda bu hatayı yapmamak için bağırsakları kodlayabilir.
Fabien Haddadi

Yanıtlar:


469

Gönderen Kayan Nokta Rehberi :

Bu sorunu önlemek için ne yapabilirim?

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

  • Sonuçlarınızı tam olarak toplamak için gerçekten ihtiyacınız varsa, özellikle parayla çalışırken: özel bir ondalık veri türü kullanın.
  • Tüm bu ekstra ondalık basamakları görmek istemiyorsanız: sonucunuzu görüntülerken sabit sayıda ondalık basamağa yuvarlayın.
  • Ondalık veri türü yoksa, alternatif tamsayılarla çalışmaktır, örneğin tamamen sentlerde para hesaplamaları yapmak. Ama bu daha fazla iş ve bazı dezavantajları var.

İlk noktanın yalnızca belirli kesin ondalık davranışa gerçekten ihtiyacınız varsa geçerlidir . Çoğu insan buna ihtiyaç duymaz, sadece 1/3 gibi durumlarda aynı hatada yanıp sönmeyeceklerini fark etmeden programlarının 1/10 gibi sayılarla düzgün çalışmadığından rahatsız olurlar.

İlk nokta sizin için gerçekten geçerliyse, JavaScript için BigDecimal'i kullanın , bu hiç de zarif değildir, ancak gerçekte kusurlu bir geçici çözüm sağlamak yerine sorunu çözer.


11
BigDecimal için ölü bağlantınızı fark ettim ve bir ayna ararken, BigNumber adlı bir alternatif buldum: jsfromhell.com/classes/bignumber
Jacksonkr

4
@ bass-t: Evet, ancak şamandıralar tam olarak anlamlılığın uzunluğuna kadar tam sayıları temsil edebilir ve ECMA standardına göre 64bit şamandıradır. Böylece tam olarak 2 ^ 52'ye kadar tam sayıları temsil edebilir
Michael Borgwardt

5
@Karl: 1/10 ondalık kesir, taban 2'de sonlu bir ikili kesir olarak temsil edilemez ve Javascript sayıları budur. Bu yüzden olduğu aslında tam olarak aynı sorun.
Michael Borgwardt

12
Bugün tamsayıların bile javascriptte hassas problemleri olduğunu öğrendim. console.log(9332654729891549)Aslında yazdırdığını düşünün 9332654729891548(yani bir tarafından kapalı!)
15'te

12
@mlathe: Doh .. ;P... 2⁵²= 4,503,599,627,370,496ve 2⁵³= 9,007,199,254,740,992arasında temsil edilebilir sayılar tamsayıdır . Bir sonraki aralığı için, gelen 2⁵³için 2⁵⁴, her şey olduğu ile çarpılarak2 gösterilebilir numaralarını yani, hatta olanları , vb önceki aralığı için Tersine, 2⁵¹için 2⁵², aralık olduğunu 0.5, vb tabanı azalan | | basitçe artan nedeniyle budur radix 2 | ikilik üs bölgesi / (sırayla nadiren belgelenmiş 'beklenmedik davranışlarını açıklar 64-bit kayar nokta değerinin toPrecision()arasındaki değerleri için 0ve 1).
GitaarLAB

126

Pedro Ladaria'nın çözümünü seviyorum ve benzer bir şey kullanıyorum.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

Pedros çözümünün aksine, bu 0.999 ... tekrarlar ve en az anlamlı basamakta artı / eksi bir doğrudur.

Not: 32 veya 64 bit şamandıralarla uğraşırken, en iyi sonuçlar için toPrecision (7) ve toPrecision (15) öğelerini kullanmalısınız. Nedeniyle ilgili bilgi için bu soruya bakın .


21
12 seçmenizin bir nedeni var mı?
qwertymk

18
toPrecisionsayı yerine dize döndürür. Bu her zaman arzu edilmeyebilir.
SStanley

7
Hassasiyet (3) => 1.00
Peter

5
@ user2428118, biliyorum, yuvarlama hatasını göstermek istedim, sonuç 1,01 yerine 1,00
Peter

9
@ User2428118'in söyledikleri yeterince açık olmayabilir: 49.95 yerine (9.99*5).toPrecision(2)= 50 , toPrecision yalnızca ondalık sayıları değil, tam sayıyı sayar. Daha sonra kullanabilirsiniz , ancak sonucunuz> 100 ise, yine şansınız kalmaz, çünkü ilk üç sayıya ve bir ondalığa izin verir, bu şekilde noktayı kaydırır ve bunu az çok kullanılamaz hale getirir. toPrecision(4)toFixed(2)
Onun

79

Matematiksel olarak eğimli için: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Önerilen yaklaşım düzeltme faktörlerini kullanmaktır (aritmetiğin tamsayılar arasında gerçekleşmesi için 10'luk uygun bir güçle çarpın). Örneğin 0.1 * 0.2, düzeltme faktörü 10, ve hesaplamayı yapıyorsunuz:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

(Çok hızlı) bir çözüm şuna benzer:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

Bu durumda:

> Math.m(0.1, 0.2)
0.02

SinfulJS gibi test edilmiş bir kütüphane kullanmanızı kesinlikle tavsiye ederim


1
Bu zarif geçici çözümü seviyorum ancak mükemmel görünmüyor : jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45), 115.10000000000002
nicolallias

3
Math.m (10,2332226616) bana "-19627406800" veriyor, bu negatif bir değer ... Umarım bir üst sınır olmalı - bu soruna neden olabilir. Lütfen önerin
Shiva Komuravelly

1
Bu harika görünüyor, ama bir yerde bir veya iki hata var gibi görünüyor.
MrYellow

5
Çok hızlı bir çözüm dedi ... hiç kimsenin söylemediği kırık tamir
Cozzbie

2
Yukarıdaki kodu kullanmayın. Eğer işe yaramazsa kesinlikle 'hızlı bir çözüm' değildir. Bu matematikle ilgili bir sorudur, bu yüzden doğruluk gereklidir.
Drenai

49

Sadece çarpma işlemi mi yapıyorsunuz? Eğer öyleyse, o zaman avantajlı olarak ondalık aritmetik hakkında temiz bir sır kullanabilirsiniz. İşte bu NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Yani, eğer 0.123 * 0.12varsa, 5 ondalık basamak olacağını biliyoruz, çünkü 0.1233 ondalık basamak ve 0.12iki basamak vardır. Böylece JavaScript bize bir sayı verdiyse 0.014760000002, hassaslık kaybetme korkusu olmadan 5. ondalık yere güvenle gidebiliriz.


6
... ve ondalık basamakların tam olarak nasıl elde edileceği .
line-o

7
0.5 * 0.2 = 0.10; Yine de 2 ondalık basamakta (veya daha az) kısaltabilirsiniz. Fakat asla bu kanunun ötesinde herhangi bir matematiksel önemi olan bir sayı olmayacaktır.
Nate Zaugg

3
Bunun için bir alıntı var mı? Aynı şeyin bölme için de geçerli olmadığını unutmayın.
Griffin

3
@NateZaugg taşan ondalık sayıları kesemezsiniz, miktarı yuvarlamanız gerekir, çünkü 2090.5 * 8.61 17999.205'tir, ancak şamandırada 17999.204999999998
Lostfields

3
@Lostfields - Haklısın! Cevabımı güncelledim.
Nate Zaugg

29

sprintfJavaScript için bir uygulama arıyorsunuz , böylece küçük hatalara sahip şamandıraları (ikili biçimde saklandıkları için) beklediğiniz bir formatta yazabilirsiniz.

Javascript-sprintf'i deneyin , şöyle diyebilirsiniz:

var yourString = sprintf("%.2f", yourNumber);

numaranızı iki ondalık basamaklı bir kayan nokta olarak yazdırmak için.

Yalnızca belirli bir kesinlikteki kayan nokta yuvarlaması için daha fazla dosya eklemek istemiyorsanız, Number.toFixed () öğesini görüntüleme amacıyla da kullanabilirsiniz .


4
Bence bu en temiz çözüm. Sonucun gerçekten 0.02 olması gerekmedikçe, küçük hata ihmal edilebilir. Önemli olan, numaranızın hoş bir şekilde görüntülenmesi değil, keyfi bir hassasiyetiniz olduğu gibi görünüyor.
Uzun Ouyang

2
Görüntülemek için bu gerçekten en iyi seçenektir, karmaşık hesaplamalar için Borgwardt'ın cevabını kontrol edin.
Müsait değil

4
Ama sonra tekrar bu, yourNumber.toFixed (2) ile tam olarak aynı dizeyi döndürür.
Robert

27

Ben buluyorum BigNumber.js benim ihtiyaçlarını karşılar.

Rasgele kesinlikli ondalık ve ondalık olmayan aritmetik için bir JavaScript kitaplığı.

İyi belgelere sahiptir ve yazar geri bildirimlere çok titiz davranır.

Aynı yazarın 2 benzer kütüphanesi var:

Big.js

Rasgele kesinlikli ondalık aritmetik için küçük, hızlı bir JavaScript kitaplığı. Bignumber.js'nin küçük kız kardeşi.

ve Decimal.js

JavaScript için rasgele kesinlikli Ondalık tür.

İşte BigNumber kullanarak bazı kodlar:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>


3
Bence bir kütüphane kullanmak kesinlikle en iyi seçim.
Anthony

1
Bu bağlantıdan github.com/MikeMcl/big.js/issues/45 bignumber.js -> finansal decimal.js -> bilimsel big.js -> ???
vee

20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---veya---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---Ayrıca---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- de olduğu gibi ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

4
Bunun sonuç olarak aynı sorunu vereceğini düşünüyorum. Bir kayan noktayı döndürürsünüz, böylece dönüş değeri de "yanlış" olur.
Gertjan

1
Çok zeki ve kullanışlı, +1.
Jonatas Walker

18

Bu işlev, iki kayar nokta sayısının çarpılmasıyla gereken hassasiyeti belirler ve uygun hassasiyetle bir sonuç döndürür. Olmasa da zarif.

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

Ew. Evet, sayıları kayan nokta matematiği için dizelere dönüştürelim ve bunu bir cevap olarak önerelim.
Andrew

17

Şaşırtıcı bir şekilde, diğerlerinin de benzer varyasyonları olmasına rağmen, bu işlev henüz gönderilmemiştir. Math.round () için MDN web dokümanlarından alınmıştır. Özlü ve değişken hassasiyet sağlar.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (accuracyRound (1234.5678, 1)); // beklenen çıktı: 1234.6

console.log (accuracyRound (1234.5678, -1)); // beklenen çıktı: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

GÜNCELLEME: 20 Ağustos 2019 Bu hatayı fark ettim. Math.round () ile bir kayan nokta hassas hatası nedeniyle inanıyorum.

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Bu koşullar düzgün çalışır:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Düzeltme:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

Bu, ondalık sayıları yuvarlarken sağa bir rakam ekler. MDN Math.round sayfasını güncelledi, böylece birisi daha iyi bir çözüm sağlayabilir.


yanlış cevap. 10.2 her zaman 10.19 döndürür. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas

@ Vinilvinas Gönderdiğiniz JSBin bağlantısı yukarıda listelenen MDN işlevini kullanmaz. Bence yorumunuz yanlış kişiye yönelik.
HelloWorldPeace

13

Sadece kaç ondalık basamak istediğinizi kararlaştırmanız yeterlidir - pastaya sahip olamaz ve onu da yiyemez :-)

Sayısal hatalar daha sonraki her işlemde birikir ve erken kesmezseniz büyüyecektir. Temiz görünen sonuçlar sunan sayısal kütüphaneler her adımda son 2 haneyi keser, sayısal yardımcı işlemciler de aynı nedenden ötürü "normal" ve "tam" uzunluğa sahiptir. Cuf-off'lar bir işlemci için ucuzdur ancak bir senaryoda sizin için çok pahalıdır (çarpma ve bölme ve pov (...) kullanma). İyi matematik kütüphanesi sizin için kesme işlemini (x, n) sağlayacaktır.

Yani en azından pov (10, n) ile küresel var / sabit yapmalısınız - yani ihtiyacınız olan hassasiyete karar verdiniz :-) Sonra yapın:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

Ayrıca matematik yapmaya devam edebilir ve sonunda sadece kesme yaptığınızı ve sonuçlarla if-s'leri yapmadığınızı varsayarak, yalnızca kesme işlemini sonlandırabilirsiniz. Bunu yapabilirseniz, .toFixed (...) daha verimli olabilir.

Eğer if-s / karşılaştırmaları yapıyorsanız ve kesmek istemiyorsanız, aynı zamanda, genellikle eps olarak adlandırılan, beklenen maksimum hatadan bir ondalık basamak daha yüksek olan küçük bir sabite ihtiyacınız vardır. Kesiminizin son iki ondalık sayı olduğunu varsayalım - o zaman eps'niz sondan 3. sırada (1 en az anlamlı) 1'e sahip ve sonucun beklenen eps aralığında (0.02 -eps <0.1) olup olmadığını karşılaştırmak için kullanabilirsiniz. * 0.2 <0.02 + eps).


Zavallı bir adamın yuvarlamasını yapmak için 0,5 de ekleyebilirsiniz: Math.floor (x * PREC_LIM + 0,5) / PREC_LIM
cmroanirgo

Not olsa o örn Math.floor(-2.1)olduğunu -3. Belki de örneğin kullanınMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM

Neden flooryerine round?
Quinn Comendant

12

Küçük bir işlem için bu sorunu kullanabilir parseFloat()ve toFixed()atlamak isterseniz:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;

11

Phpjs.org'daki round () işlevi iyi çalışır: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07

2
@jrg Kural olarak, "5" ile biten sayılar en yakın düzeye yuvarlanır (çünkü daima yukarı veya aşağı yuvarlama sonuçlarınıza bir sapma getirecektir). Bu nedenle, iki ondalık basamağa yuvarlanan 4.725 gerçekten 4.72 olmalıdır.
Mark A. Durham

9

0.6 * 3 harika!)) Benim için bu iyi çalışıyor:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Çok çok basit))


Böyle bir şey olsa işe yarar 8.22e-8 * 1.3mı?
Paul Carlton

0.6 x 3 = 1.8, 2'ye sonuç verdiğiniz kod ... bu yüzden iyi değil.
Zyo

@Zyo Bu örnekte 1.8 döndürür. Nasıl çalıştırdın?
Drenai

İlginç. Çarpma ve bölme operatörlerini bu şekilde değiştirebilirsiniz ve ayrıca çalışır.
Andrew

9

Genel amaçlı kullanımda, bu davranışın kabul edilebilir olduğuna dikkat edin.
Sorun, uygun bir eylem belirlemek için bu kayan nokta değerleri karşılaştırılırken ortaya çıkar.
ES6'nın ortaya çıkmasıyla Number.EPSILON, kabul edilebilir hata payını belirlemek için yeni bir sabit tanımlanır:
Yani, karşılaştırmayı böyle yapmak yerine

0.1 + 0.2 === 0.3 // which returns false

aşağıdaki gibi özel bir karşılaştırma işlevi tanımlayabilirsiniz:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Kaynak: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon


Benim durumumda Number.EPSILON çok küçüktü, sonuçta0.9 !== 0.8999999761581421
Tom

8

Elde ettiğiniz sonuç, farklı dillerde, işlemcilerde ve işletim sistemlerinde kayan nokta uygulamalarında doğru ve oldukça tutarlıdır - değişen tek şey, şamandıra aslında bir çift (veya daha yüksek) olduğunda yanlışlık seviyesidir.

İkili kayan noktalarda 0,1 ondalık ondalık 1/3 gibidir (yani 0.3333333333333 ... sonsuza dek), bununla başa çıkmanın doğru bir yolu yoktur.

Şamandıralarla uğraşıyorsanız, her zaman küçük yuvarlama hataları beklersiniz, bu nedenle görüntülenen sonucu her zaman mantıklı bir şeye yuvarlamanız gerekir. Buna karşılık, çok hızlı ve güçlü bir aritmetik elde edersiniz çünkü tüm hesaplamalar işlemcinin doğal ikili dosyasındadır.

Çoğu zaman çözüm, sabit nokta aritmetiğine geçmek değildir, çünkü esas olarak çok daha yavaştır ve zamanın% 99'u doğruluk oranına ihtiyacınız yoktur. Bu doğruluk düzeyine ihtiyaç duyan şeylerle uğraşıyorsanız (örneğin finansal işlemler) Javascript muhtemelen yine de kullanmak için en iyi araç değildir (sabit nokta türlerini uygulamak istediğinizde statik bir dil muhtemelen daha iyidir ).

Zarif çözümü arıyorsunuz, o zaman korkarım: şamandıralar hızlı ama küçük yuvarlama hataları var - sonuçlarını görüntülerken her zaman mantıklı bir şeye yuvarlak.


8

Bundan kaçınmak için kayan noktalar yerine tamsayı değerleriyle çalışmalısınız. 2 pozisyona sahip olmak istediğinizde * 100 değerleri ile hassas bir şekilde çalışın, 3 pozisyon için 1000 kullanın. Gösterirken ayırıcıya koymak için bir formatlayıcı kullanın.

Birçok sistem ondalıklarla çalışmayı bu şekilde atlar. Birçok sistemin dolar / euro (kayan nokta) yerine sentlerle (tamsayı olarak) çalışmasının nedeni budur.


7

Sorun

Kayan nokta tüm ondalık değerleri tam olarak depolayamaz. Bu nedenle kayan nokta formatlarını kullanırken giriş değerlerinde her zaman yuvarlama hataları olacaktır. Girişlerdeki hatalar elbette çıktıdaki hatalarla sonuçlanır. Bir ayrık fonksiyon veya operatör durumunda, fonksiyon veya operatörün ayrık olduğu nokta etrafında çıkışta büyük farklılıklar olabilir.

Kayan nokta değerleri için giriş ve çıkış

Bu nedenle, kayan nokta değişkenlerini kullanırken her zaman bunun farkında olmalısınız. Kayan noktalı bir hesaplamadan istediğiniz çıktı, bunu göz önünde bulundurarak her zaman biçimlendirilmeli / koşullandırılmalıdır.
Yalnızca sürekli işlevler ve işleçler kullanıldığında, istenen hassasiyete yuvarlama sıklıkla yapılır (kesmeyin). Şamandıraları dizeye dönüştürmek için kullanılan standart biçimlendirme özellikleri bunu genellikle sizin için yapar.
Yuvarlama, toplam hatanın istenen hassasiyetin yarısından daha fazla olmasına neden olabilecek bir hata eklediğinden, çıktı, beklenen girdi hassasiyeti ve istenen çıktı hassasiyeti temelinde düzeltilmelidir. Malısın

  • Girişleri beklenen hassasiyete yuvarlayın veya daha yüksek hassasiyetle hiçbir değerin girilemediğinden emin olun.
  • Yuvarlama / biçimlendirme işleminden önce, istenen hassasiyetin 1 / 4'ünden küçük veya ona eşit ve girişteki ve hesaplama sırasındaki yuvarlama hatalarından kaynaklanan maksimum beklenen hatadan daha büyük olan çıkışlara küçük bir değer ekleyin. Bu mümkün değilse, kullanılan veri türünün kesinliğinin kombinasyonu, hesaplamanız için istenen çıktı hassasiyetini sağlamak için yeterli değildir.

Bu 2 şey genellikle yapılmaz ve çoğu durumda bunları yapmamaktan kaynaklanan farklar çoğu kullanıcı için önemli olamayacak kadar küçüktür, ancak zaten bu düzeltmeler olmadan çıktının kullanıcılar tarafından kabul edilmediği bir projem vardı.

Ayrık fonksiyonlar veya operatörler (modula gibi)

Ayrık işleçler veya işlevler söz konusu olduğunda, çıktının beklendiği gibi olduğundan emin olmak için ek düzeltmeler gerekebilir. Yuvarlamadan önce yuvarlama ve küçük düzeltmeler ekleme sorunu çözemez.
Kesikli fonksiyon veya operatör uygulandıktan hemen sonra ara hesaplama sonuçlarında özel bir kontrol / düzeltme yapılması gerekebilir. Belirli bir durum için (modula operatörü), şu soruya cevabım bakın: Modül operatörü neden javascript'te kesirli sayı döndürüyor?

Soruna sahip olmaktan daha iyi

Bu tür hesaplamalarda, yuvarlama hataları olmadan beklenen girişi depolayabilen veri türlerini (tamsayı veya sabit nokta formatları) kullanarak bu sorunlardan kaçınmak genellikle daha etkilidir. Bunun bir örneği, finansal hesaplamalar için asla kayan nokta değerleri kullanmamanızdır.


4

Sabit noktalı aritmetiğe bir göz atın . Üzerinde işlem yapmak istediğiniz sayı aralığı (ör. Para birimi) küçükse, muhtemelen sorununuzu çözecektir. En basit çözüm olan birkaç ondalık değere yuvarlayacağım.


5
Sorun kayan noktaya karşı sabit nokta değil, sorun ikiliye karşı ondalıktır.
Michael Borgwardt

4

Burada görebileceğiniz chiliadic aritmetik kütüphanemi deneyin . Daha sonraki bir sürümü istiyorsanız, size bir tane alabilirim.



4

resim açıklamasını buraya girin

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985

3

Haklısınız, bunun nedeni kayan nokta sayılarının sınırlı hassasiyeti. Rasyonel sayılarınızı iki tamsayı sayının bir bölümü olarak saklayın ve çoğu durumda sayıları hassas bir kayıp olmadan saklayabilirsiniz. Yazdırma söz konusu olduğunda, sonucu kesir olarak görüntülemek isteyebilirsiniz. Önerdiğim temsil ile bu önemsiz hale geliyor.

Tabii ki bu irrasyonel sayılarla pek yardımcı olmaz. Ancak hesaplamalarınızı en az soruna neden olacak şekilde optimize etmek isteyebilirsiniz (örn sqrt(3)^2).


Haklısınız, bunun nedeni kayan nokta sayılarının sınırlı hassasiyeti - <pedant>aslında, OP bunu kayan nokta işlemlerini kesin olmayan bir şekilde ortaya koydu, bu yanlış</pedant>
Ağustos'ta

3

Mod 3'te kötü bir yuvarlama hatası sorunum vardı. Bazen 0 almam gerektiğinde .000 ... 01 alırdım. İşlenmesi kolay, sadece <= .01 için test edin. Ama sonra bazen 2.99999999999998 alırdım. Ah!

BigNumbers sorunu çözdü, ama biraz ironik bir sorun daha getirdi. 8.5'i BigNumbers'a yüklemeye çalışırken, bunun gerçekten 8.4999 olduğu ve 15'ten fazla önemli basamağı olduğu konusunda bilgilendirildim. Bu BigNumbers'ın kabul edemeyeceği anlamına geliyordu (bu sorunun biraz ironik olduğunu söyledim).

İronik probleme basit çözüm:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);

2

Numarayı (1.234443) kullanın. ToFixed (2); 1.23 yazdıracak

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();

2

Javascript'te kayan nokta manipülasyon sorunlarını önlemek için decimal.js , big.js veya bignumber.js kullanılabilir:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimalist; kullanımı kolay; ondalık basamaklarda belirtilen hassasiyet; kesinlik yalnızca bölmeye uygulanır.

bignumber.js: baz 2-64; yapılandırma seçenekleri; NaN; Sonsuzluk; ondalık basamaklarda belirtilen hassasiyet; kesinlik yalnızca bölmeye uygulanır; temel önekler.

decimal.js: baz 2-64; yapılandırma seçenekleri; NaN; Sonsuzluk; tamsayı olmayan güçler, exp, ln, log; anlamlı basamaklarda belirtilen hassasiyet; hassasiyet her zaman uygulanır; rastgele sayılar.

detaylı karşılaştırmalar bağlantısı


2

Zarif, Öngörülebilir ve Yeniden Kullanılabilir

Sorunu zarif bir şekilde yeniden kullanılabilir bir şekilde ele alalım. Aşağıdaki yedi satır .decimal, sayının, formülün veya yerleşik Mathişlevin sonuna ekleyerek herhangi bir sayı üzerinde istediğiniz kayan nokta hassasiyetine erişmenizi sağlayacaktır .

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Şerefe!


2
İndirmeyi seçerseniz, en azından bir neden belirtin.
Bernesto

1

kullanım

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);

4
Hmm ... ama not et, bu her zaman 2 ondalığa yuvarlar. Tabii ki bu bir seçenek olurdu, ama 0.55 * 0.55 hesaplaması ne olacak (kesin sayıları önceden bilmediğimden 0.3025 yerine 0.3 verecekti. Tabii ki kullanabilirim Math.round(x*Math.pow(10,4))/Math.pow(10,4);. Yuvarlama her zaman bir seçenektir ama daha iyi bir çözüm olup olmadığını bilmek istedim
Juri



1

Bu benim için çalışıyor:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54

1
ne yazık ki, round_up (4.15,2) => 4.16.
jrg

1

Aşağıdaki işlevi kullanarak çıktı:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

Çıktıya dikkat edin toFixedCurrency(x).

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.