首頁 web前端 js教程 解決JavaScript數位精度遺失問題的方法_javascript技巧

解決JavaScript數位精度遺失問題的方法_javascript技巧

May 16, 2016 pm 03:28 PM

本文分為三個部分

  • JS 數位精確度遺失的一些典型問題
  • JS 數位精確度遺失的原因
  • 解(一個物件 一個函數)

一、JS數位精確度遺失的一些典型問題

1. 兩個簡單的浮點數相加

0.1 + 0.2 != 0.3 // true
登入後複製

這真是不是 Firebug 的問題,可以用alert試試 (哈哈開玩笑)。

看看Java的運算結果

再看Python

2. 大整數運算

16位和17位數竟然相等,沒天理啊。

又如

var x = 9007199254740992
x + 1 == x // ?
登入後複製

看結果

三觀又被顛覆了。

3. toFixed 不會四捨五入(Chrome)

線上曾經發生過 Chrome 中價格和其它瀏覽器不一致

二、JS 數位遺失精確度的原因

電腦的二進位實作和位數限制有些數無法有限表示。就像一些無理數不能有限表示,如 圓周率 3.1415926...,1.3333... 等。 JS 遵循 IEEE 754 規範,採用雙精度儲存(double precision),佔用 64 bit。如圖

意義

  • 1位元用來表示符號位元
  • 11位元用來表示指數
  • 52位表示尾數

浮點數,例如

0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
0.2 >> 0.0011 0011 0011 0011…(0011无限循环)
登入後複製

此時只能模仿十進制進行四捨五入了,但是二進位只有 0 和 1 兩個,於是變成 0 舍 1 入。這也就是計算機中部分浮點數運算時出現誤差,丟失精度的根本原因。

大整數的精確度遺失和浮點數本質上是一樣的,尾數位最大是52位,因此 JS 中能精確表示的最大整數是 Math.pow(2, 53),十進位即 9007199254740992。

大於 9007199254740992 的可能會失去精度

9007199254740992  >> 10000000000000...000 // 共计 53 个 0
9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0
9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0
登入後複製

實際上

9007199254740992 + 1 // 丢失
9007199254740992 + 2 // 未丢失
9007199254740992 + 3 // 丢失
9007199254740992 + 4 // 未丢失
登入後複製

結果如圖

以上,可以知道看似有窮的數字, 在計算機的二進製表示裡卻是無窮的,由於存儲位數限制因此存在“捨去”,精度丟失就發生了。

三、解

對於整數,前端出現問題的幾率可能比較低,畢竟很少有業務需要需要用到超大整數,只要運算結果不超過 Math.pow(2, 53) 就不會丟失精度。

對於小數,前端出現問題的幾率還是很多的,尤其在一些電商網站涉及到金額等數據。解法:把小數放到位整數(乘倍數),再縮小回原來倍數(除倍數)

// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true

登入後複製

以下是我寫了一個對象,對小數的加減乘除運算丟失精度做了屏蔽。當然轉換後的整數依然不能超過 9007199254740992。

/**
 * floatObj 包含加减乘除四个方法,能确保浮点数运算不丢失精度
 *
 * 我们知道计算机编程语言里浮点数计算会存在精度丢失问题(或称舍入误差),其根本原因是二进制和实现位数限制有些数无法有限表示
 * 以下是十进制小数对应的二进制表示
 *  0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
 *  0.2 >> 0.0011 0011 0011 0011…(0011无限循环)
 * 计算机里每种数据类型的存储是一个有限宽度,比如 JavaScript 使用 64 位存储数字类型,因此超出的会舍去。舍去的部分就是精度丢失的部分。
 *
 * ** method **
 * add / subtract / multiply /divide
 *
 * ** explame **
 * 0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004)
 * 0.2 + 0.4 == 0.6000000000000001 (多了 0.0000000000001)
 * 19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatObj = function() {
 
 /*
  * 判断obj是否为一个整数
  */
 function isInteger(obj) {
  return Math.floor(obj) === obj
 }
 
 /*
  * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
  * @param floatNum {number} 小数
  * @return {object}
  * {times:100, num: 314}
  */
 function toInteger(floatNum) {
  var ret = {times: 0, num: 0}
  if (isInteger(floatNum)) {
   ret.num = floatNum
   return ret
  }
  var strfi = floatNum + ''
  var dotPos = strfi.indexOf('.')
  var len = strfi.substr(dotPos+1).length
  var times = Math.pow(10, len)
  var intNum = parseInt(floatNum * times + 0.5, 10)
  ret.times = times
  ret.num = intNum
  return ret
 }
 
 /*
  * 核心方法,实现加减乘除运算,确保不丢失精度
  * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
  *
  * @param a {number} 运算数1
  * @param b {number} 运算数2
  * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
  * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
  *
  */
 function operation(a, b, digits, op) {
  var o1 = toInteger(a)
  var o2 = toInteger(b)
  var max = o1.times > o2.times ? o1.times : o2.times
  var result = null
  switch (op) {
   case 'add':
    result = o1.num + o2.num
    break
   case 'subtract':
    result = o1.num - o2.num
    break
   case 'multiply':
    result = o1.num * o2.num
    break
   case 'divide':
    result = o1.num / o2.num
    break
  }
  return result / max
 }
 
 // 加减乘除的四个接口
 function add(a, b, digits) {
  return operation(a, b, digits, 'add')
 }
 function subtract(a, b, digits) {
  return operation(a, b, digits, 'subtract')
 }
 function multiply(a, b, digits) {
  return operation(a, b, digits, 'multiply')
 }
 function divide(a, b, digits) {
  return operation(a, b, digits, 'divide')
 }
 
 // exports
 return {
  add: add,
  subtract: subtract,
  multiply: multiply,
  divide: divide
 }
 
}();
登入後複製

toFixed的修復如下

// toFixed 修复
function toFixed(num, s) {
 var times = Math.pow(10, s)
 var des = num * times + 0.5
 des = parseInt(des, 10) / times
 return des + ''
}
登入後複製

以上就是關於JavaScript數位精度遺失問題全部內容,對典型問題進行分析,分析數位精確度遺失原因,也分享了解決方案,希望對大家的學習有所幫助。

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1242
24
神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript引擎:比較實施 JavaScript引擎:比較實施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript:探索網絡語言的多功能性 JavaScript:探索網絡語言的多功能性 Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

如何使用Next.js(前端集成)構建多租戶SaaS應用程序 如何使用Next.js(前端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

使用Next.js(後端集成)構建多租戶SaaS應用程序 使用Next.js(後端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

從C/C到JavaScript:所有工作方式 從C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

See all articles