JSON實戰拾遺之數字精度

阿狸不歌發表於2018-08-09

JSON實戰封面

正如《JSON實戰》書中所言 —— “JSON已經成為 RESTful 介面設計中的事實標準”,作為一個寫程式的,我們很難避免與JSON打交道。

JSON從本質上講就是一類字串,所以在第二章(“在 JavaScript 中使用 JSON”)裡一開始就向我們介紹了JSON的序列化 / 反序列化操作。用JSON.stringify() 將資訊序列化為 JSON(字串);用JSON.parse() 將 JSON 反序列化為 JavaScript 可以理解的資料結構。

例如我們在node.js控制檯或者chrome控制檯裡輸入:

JSON.stringify({"數字": 12345678901234567890})

會得到:

'{"數字":12345678901234567000}'

再例如,輸入:

JSON.parse('{"數字":12345678901234567890}')

會得到

{ '數字': 12345678901234567000 }

看看,JSON的序列化和反序列化就是如此簡單!

但是,如果我們仔細的看看序列化和反序列化的結果,就會喊出WTF —— 我明明輸入的數字是 12345678901234567890,怎麼得到的卻是12345678901234567000 ??

為什麼會出現這樣的結果?其實當你用的數字比較小的時候,是碰不上這個問題的,只有當你用的數字足夠大(超過 Number.MAX_SAFE_INTEGER),或者足夠小(小於 Number.MIN_SAFE_INTEGER)的時候,這個問題才會浮現,按照MDN上的描述是這樣的 ——

形成這個數字的原因是 JavaScript 在 IEEE 754中使用double-precision floating-point format numbers 作為規定。在這個規定中能安全的表示數字的範圍在-(253 - 1) 到 253 - 1之間.


怎麼辦?

是否要解決這個問題取決於你的業務場景。

如果你確信你的程式不會涉及到上面那麼大的數,那就放心的使用JSON.parse和JSON.stringify好了(真是廢話!?)


後端之間傳遞

如果你只是在後端使用JSON,那麼選擇方案還是挺多的,比如node.js裡有一些庫 lossless-jsonjson-bigint 可以解決精度的問題。再比如python就不存在這個問題(所以沒人用javascript去做科學計算啊 ?)。


前後端之間傳遞

如果要在前端進行一些互動,比如讓使用者輸入數字再轉成JSON傳遞給後端,那麼問題就出現了:怎麼保證前端輸入的數字在前端被轉為JSON的時候不走樣呢?我找了一圈之後發現答案其實挺簡單,那就是你把在前端輸入的數字就當成一個字串,轉成JSON就不會走樣。

在JS控制檯裡輸入: JSON.stringify({"數字": "12345678901234567890"})

得到: '{"數字":"12345678901234567890"}'

當這個寫成字串的JSON被傳遞到後端以後,根據後端用的語言不同,有不同的處理方法。

例如

Python

python做後端的只要直接把數字字串轉成需要的數字型別即可,例如:

int(json.loads('{"數字":"12345678901234567890"}')['數字'])

得到 12345678901234567890


Node.js

可以利用 decimal.jsbignumber.js 這樣的庫把 字串轉為"big number" 再進行計算:

   var BigNumber = require('bignumber.js');
   數字 = BigNumber('12345678901234567890');
   數字.plus('987654321')

小結

JSON在Javascript的序列化與反序列化當中存在的數字精度其實是一個比較常見的問題,如果你在使用JS處理JSON數字時得到了一些莫名其妙的結果,請考慮一下是不是遇到了這個問題。

相關文章