# JavaScript 浮點數無誤差四捨五入改良版

Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
JavaScript 浮點數無誤差四捨五入改良版-黑暗執行緒

1. 原先用整數跟小數總計 16 位判斷出現 999X 或 0000X 近似值誤差，但 JavaScript Number 規格位數上限是 17 位(A Number only keeps about 17 decimal places of precision)，發生近似值誤差時的位數可能是 16 位或 17 位，例如：
1.1 + 3.2 = 4.300000000000001 不含小數點共 16 位
4.17 * 3.251 = 13.556669999999999 不含小數點共 17 位
2. 即使簡單的 * 100 也可能出現浮點數誤差，例如：1.005 * 100 = 100.49999999999999，在計算 Math.round(n * 100) / 100 取小數兩位時完全沒考慮到這點

``````<%@Page Language="C#"%>
<script runat="server">
class TestCase
{
public decimal a { get; set; }
public decimal b { get; set; }
public int n { get; set; }
public decimal ans =>
(decimal)Math.Round(a + b, n, MidpointRounding.AwayFromZero);
}
string TestCasesJson()
{
var cases = new List<TestCase>();
var rnd = new Random(9527);
for (int i = 0; i < 100000; i++)
{
{
a = (decimal)Math.Round(
rnd.NextDouble() * 100, rnd.Next(5), MidpointRounding.AwayFromZero),
b = (decimal)Math.Round(
rnd.NextDouble() * 100, rnd.Next(5), MidpointRounding.AwayFromZero),
n = rnd.Next(5)
});
}
return new System.Web.Script.Serialization.JavaScriptSerializer() {
MaxJsonLength = int.MaxValue
}.Serialize(cases);
}
</script>

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<div id=status></div>
<div id=msg>
</div>
<script>
function safeRound(v, n) {
// 含小數達16位數時，小數部分四捨五入減少一位
// 注意：若該精確度極限數字非浮點運算產生時會被喪失一位精確度
let p = v.toString().split('.');
if (p.length == 2) {
let intLen = p[0].length;
let decLen = p[1].length;
if (intLen + decLen >= 16) {
let tt = Math.pow(10, decLen - 1);
v = Math.round(v * tt) / tt;
}
}
let t = Math.pow(10, n);
return Math.round(v * t) / t;
}
var testCases = <%=TestCasesJson()%>;
var count = 0;
testCases.forEach(function(test, i) {
var r = safeRound(test.a + test.b, test.n);
if (r != test.ans) {
document.getElementById('msg').innerHTML +=
'<li>' + (++count) + '. TEST FAILED: Round(' + test.a + ' + ' + test.b + ', ' + test.n + ') = '
+ r + ' (' + test.ans + ' expected)</li>';
}
document.getElementById('status').innerText =
count + '/' + (i + 1) + ' (' + (count / (i + 1) * 100) + '%)';
});
</script>
<body>
</html>
``````

``````function safeRound(v, n) {
if (v % 1 !== 0) {
v = parseFloat(v.toPrecision(15));
}
var t = Math.pow(10, n);
var n = v * t;
if (n % 1 !== 0) {
n = parseFloat(n.toPrecision(15));
}
return Math.round(n) / t;
}
``````