Bu soru karmaşık.
roundTo2DP(num)
Bir şamandırayı bağımsız değişken olarak alan ve 2 ondalık basamağa yuvarlanmış bir değer döndüren bir fonksiyonumuz olduğunu varsayalım . Bu ifadelerin her biri neyi değerlendirmelidir?
roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)
'Açık' cevap ilk örneğin 0,01'e yuvarlanmasıdır (çünkü 0,02'den 0,02'ye daha yakındır), diğer ikisi 0,02'ye yuvarlanmalıdır (çünkü 0,0150000000000000001 0,02'den 0,02'ye daha yakındır ve 0,015, ve bu sayıların yuvarlandığı matematiksel bir kural vardır).
Tahmin edebileceğiniz yakalamak, yani roundTo2DP
muhtemelen olamaz kendisine geçirilen her üç sayı olduğu için, bu bariz cevap vermek uygulanacaktır aynı sayıda . IEEE 754 ikili kayan nokta sayıları (JavaScript tarafından kullanılan tür) tamsayı olmayan sayıların çoğunu tam olarak temsil edemez ve bu nedenle yukarıdaki üç sayısal değişmez değerin tümü yakındaki geçerli bir kayan nokta numarasına yuvarlanır. Bu sayı, olduğu gibi, tam olarak
0,01499999999999999944488848768742172978818416595458984375
0.01'e 0.02'den daha yakın olan.
Her üç sayının da tarayıcı konsolunuzda, Düğüm kabuğunda veya diğer JavaScript yorumlayıcılarında aynı olduğunu görebilirsiniz. Sadece karşılaştırın:
> 0.014999999999999999 === 0.0150000000000000001
true
Ben yazdığınızda m = 0.0150000000000000001
, kesin değerim
ben ile sonuna kadar daha yakın 0.01
o kadar olandan 0.02
. Ve yine de, eğer m
bir String'e dönüştürürsem ...
> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015
... Ben, 0.015 olsun hangi gerektiği yuvarlak 0.02 ve belirgin olan değil daha önce bu sayıların tümü tam olduğunu için eşit olduğunu söyledi 56-nokta-yer numarası. Peki bu ne karanlık sihir?
Cevap, ECMAScript spesifikasyonunda, 7.1.12.1: Numara türüne uygulanan ToString bölümünde bulunabilir . Burada, bazı Number m değerlerini bir String'e dönüştürmek için kurallar belirlenmiştir. Anahtar kısım nokta 5 olup, içinde m değerleri String gösteriminde basamakları kullanılacak bir tamsayı s oluşturulur :
izin n , k , ve s tamsayıları öyle ki k ≥ 1, 10 k -1 ≤ s <10 k için sayı değeri S * 10 N - k olan m ve k mümkün olduğu kadar küçük olduğu gibi. K'nin s'nin ondalık gösterimindeki basamak sayısı olduğunu, s'nin 10 ile bölünemediğini ve en küçük anlamlı s basamağının mutlaka bu kriterler tarafından belirlenmediğini unutmayın.
Burada kilit nokta, " k'nin mümkün olduğunca küçük olması" gerekliliğidir . Bu gereksinimin ne anlama geldiği, bir Sayı verildiğinde m
, değerinin mümkün olan en az sayıyaString(m)
sahip olması ve yine de bu gereksinimi karşılaması gereken bir gerekliliktir Number(String(m)) === m
. Bunu zaten bildiğimiz için 0.015 === 0.0150000000000000001
, neden String(0.0150000000000000001) === '0.015'
doğru olması gerektiği açık .
Tabii ki, bu tartışmanın hiçbiri neyin geri dönmesi roundTo2DP(m)
gerektiğini doğrudan cevaplamadı . Eğer m
'bireyin tam değer 0,01499999999999999944488848768742172978818416595458984375, ama onun dize temsilidir' 0.015, o zaman ne olduğunu doğru matematiksel, pratik olarak, felsefi olarak, ya da her türlü - - cevap tekrar iki ondalık basamağa o yuvarlak zaman?
Bunun tek bir doğru cevabı yok. Kullanım durumunuza bağlıdır. Muhtemelen Dize temsiline saygı göstermek ve şu durumlarda yukarı doğru yuvarlamak istiyorsunuz:
- Temsil edilen değer doğal olarak ayrıktır, örneğin dinarlar gibi 3 ondalık basamaklı bir para birimindeki para birimi miktarı. Bu durumda, gerçek 0,015 gibi bir sayı değeri olan 0.015 ve ikili kayan noktasında aldığını ,0149999999 ... temsil yuvarlama hatadır. (Tabii ki, çoğu, makul olarak, bu tür değerleri işlemek için bir ondalık kitaplık kullanmanız ve bunları asla ikili kayan nokta Sayıları olarak göstermemeniz gerektiğini iddia edecektir.)
- Değer bir kullanıcı tarafından yazılmıştır. Bu durumda, yine, girilen ondalık sayı en yakın ikili kayan nokta gösteriminden daha 'doğru' olur.
Öte yandan, değeriniz doğal olarak sürekli bir ölçekte olduğunda (örneğin, bir sensörden bir okuma ise) ikili kayan nokta değerine saygı göstermek ve aşağı doğru yuvarlamak istersiniz.
Bu iki yaklaşım farklı kodlar gerektirir. Sayının Dize temsiline saygı göstermek için (oldukça makul bir şekilde küçük bir kodla), okulda kullandığınız algoritmayı kullanarak doğrudan Dize temsiline göre basamak basamak basamak üzerinde hareket eden kendi yuvarlamamızı uygulayabiliriz sayıların nasıl yuvarlanacağı öğretildi. Aşağıda OP'nin sayıyı ondalık noktadan sonraki sıfırları sıyırarak "yalnızca gerektiğinde" 2 ondalık basamağa gösterme gereksinimini karşılayan bir örnek; elbette kesin ihtiyaçlarınıza göre ayarlamanız gerekebilir.
/**
* Converts num to a decimal string (if it isn't one already) and then rounds it
* to at most dp decimal places.
*
* For explanation of why you'd want to perform rounding operations on a String
* rather than a Number, see http://stackoverflow.com/a/38676273/1709587
*
* @param {(number|string)} num
* @param {number} dp
* @return {string}
*/
function roundStringNumberWithoutTrailingZeroes (num, dp) {
if (arguments.length != 2) throw new Error("2 arguments required");
num = String(num);
if (num.indexOf('e+') != -1) {
// Can't round numbers this large because their string representation
// contains an exponent, like 9.99e+37
throw new Error("num too large");
}
if (num.indexOf('.') == -1) {
// Nothing to do
return num;
}
var parts = num.split('.'),
beforePoint = parts[0],
afterPoint = parts[1],
shouldRoundUp = afterPoint[dp] >= 5,
finalNumber;
afterPoint = afterPoint.slice(0, dp);
if (!shouldRoundUp) {
finalNumber = beforePoint + '.' + afterPoint;
} else if (/^9+$/.test(afterPoint)) {
// If we need to round up a number like 1.9999, increment the integer
// before the decimal point and discard the fractional part.
finalNumber = Number(beforePoint)+1;
} else {
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = dp-1;
while (true) {
if (afterPoint[i] == '9') {
afterPoint = afterPoint.substr(0, i) +
'0' +
afterPoint.substr(i+1);
i--;
} else {
afterPoint = afterPoint.substr(0, i) +
(Number(afterPoint[i]) + 1) +
afterPoint.substr(i+1);
break;
}
}
finalNumber = beforePoint + '.' + afterPoint;
}
// Remove trailing zeroes from fractional part before returning
return finalNumber.replace(/0+$/, '')
}
Örnek kullanım:
> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'
Yukarıdaki işlev, kullanıcıların girdikleri sayıların yanlış yuvarlandığına tanık olmalarını önlemek için muhtemelen kullanmak istediğiniz işlevdir .
(Alternatif olarak, çılgınca farklı bir uygulama ile benzer şekilde davranan bir işlev sağlayan round10 kütüphanesini de deneyebilirsiniz .)
Peki ya ikinci tür bir Sayınız varsa - daha az ondalık basamağı olan yaklaşık ondalık temsillerin daha fazla olanlardan daha doğru olduğunu düşünmek için hiçbir nedenin olmadığı sürekli bir ölçekte alınan bir değer varsa ? Bu durumda, biz yok bu temsili (spec açıklandığı gibi) zaten, çünkü dize temsilini saygı istiyoruz sort-of-yuvarlak; "0,014999999 ... 0,05'e kadar 375 mermi, 0,02'ye kadar yuvarlama, yani 0,014999999 ... 375 mermi 0,02'ye kadar" diyerek hata yapmak istemiyoruz.
Burada yerleşik toFixed
yöntemi kullanabiliriz. Tarafından Number()
döndürülen Dize'yi çağırarak toFixed
, Dize temsilinin sonunda sıfır olmayan bir Sayı elde ettiğimizi unutmayın (JavaScript, bu cevabın başlarında tartışılan bir Sayının Dize gösterimini hesaplama yöntemi sayesinde).
/**
* Takes a float and rounds it to at most dp decimal places. For example
*
* roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
*
* returns 1.234
*
* Note that since this treats the value passed to it as a floating point
* number, it will have counterintuitive results in some cases. For instance,
*
* roundFloatNumberWithoutTrailingZeroes(0.015, 2)
*
* gives 0.01 where 0.02 might be expected. For an explanation of why, see
* http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
* roundStringNumberWithoutTrailingZeroes function there instead.
*
* @param {number} num
* @param {number} dp
* @return {number}
*/
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
var numToFixedDp = Number(num).toFixed(dp);
return Number(numToFixedDp);
}