Masalahnya dengan nilai floating point adalah mereka mencoba untuk mewakili jumlah tak terhingga dari nilai (kontinu) dengan jumlah bit yang tetap. Jadi secara alami, pasti ada beberapa kerugian dalam permainan, dan Anda akan digigit dengan beberapa nilai.
Ketika komputer menyimpan 1.275 sebagai nilai floating point, itu tidak akan benar-benar mengingat apakah itu 1.275 atau 1.27499999999999993, atau bahkan 1.27500000000000002.0. Nilai-nilai ini harus memberikan hasil yang berbeda setelah membulatkan ke dua desimal, tetapi tidak akan, karena untuk komputer mereka terlihat sama persis setelah disimpan sebagai nilai floating point, dan tidak ada cara untuk mengembalikan data yang hilang. Setiap perhitungan lebih lanjut hanya akan mengakumulasi ketidaktepatan tersebut.
Jadi, jika presisi penting, Anda harus menghindari nilai floating point dari awal. Opsi yang paling sederhana adalah
- gunakan perpustakaan khusus
- gunakan string untuk menyimpan dan mengedarkan nilai-nilai (disertai dengan operasi string)
- gunakan bilangan bulat (mis. Anda bisa memberikan sekitar seperseratus dari nilai aktual Anda, mis. jumlah dalam sen, bukan jumlah dalam dolar)
Misalnya, ketika menggunakan bilangan bulat untuk menyimpan jumlah seratus, fungsi untuk menemukan nilai sebenarnya cukup sederhana:
function descale(num, decimals) {
var hasMinus = num < 0;
var numString = Math.abs(num).toString();
var precedingZeroes = '';
for (var i = numString.length; i <= decimals; i++) {
precedingZeroes += '0';
}
numString = precedingZeroes + numString;
return (hasMinus ? '-' : '')
+ numString.substr(0, numString.length-decimals)
+ '.'
+ numString.substr(numString.length-decimals);
}
alert(descale(127, 2));
Dengan string, Anda perlu pembulatan, tetapi masih dapat dikelola:
function precise_round(num, decimals) {
var parts = num.split('.');
var hasMinus = parts.length > 0 && parts[0].length > 0 && parts[0].charAt(0) == '-';
var integralPart = parts.length == 0 ? '0' : (hasMinus ? parts[0].substr(1) : parts[0]);
var decimalPart = parts.length > 1 ? parts[1] : '';
if (decimalPart.length > decimals) {
var roundOffNumber = decimalPart.charAt(decimals);
decimalPart = decimalPart.substr(0, decimals);
if ('56789'.indexOf(roundOffNumber) > -1) {
var numbers = integralPart + decimalPart;
var i = numbers.length;
var trailingZeroes = '';
var justOneAndTrailingZeroes = true;
do {
i--;
var roundedNumber = '1234567890'.charAt(parseInt(numbers.charAt(i)));
if (roundedNumber === '0') {
trailingZeroes += '0';
} else {
numbers = numbers.substr(0, i) + roundedNumber + trailingZeroes;
justOneAndTrailingZeroes = false;
break;
}
} while (i > 0);
if (justOneAndTrailingZeroes) {
numbers = '1' + trailingZeroes;
}
integralPart = numbers.substr(0, numbers.length - decimals);
decimalPart = numbers.substr(numbers.length - decimals);
}
} else {
for (var i = decimalPart.length; i < decimals; i++) {
decimalPart += '0';
}
}
return (hasMinus ? '-' : '') + integralPart + (decimals > 0 ? '.' + decimalPart : '');
}
alert(precise_round('1.275', 2));
alert(precise_round('1.27499999999999993', 2));
Perhatikan bahwa fungsi ini membulatkan ke terdekat, mengikat jauh dari nol , sementara IEEE 754 merekomendasikan pembulatan ke terdekat, mengikat bahkan sebagai perilaku default untuk operasi floating point. Modifikasi semacam itu dibiarkan sebagai latihan bagi pembaca :)