最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JS中數(shù)學(xué)計(jì)算精度問題的解決方案

 更新時(shí)間:2023年12月03日 09:01:29   作者:凡鐵  
這篇文章主要給大家介紹了JS中數(shù)學(xué)計(jì)算精度問題的解決方案,文中通過代碼示例和圖文結(jié)合給大家講解非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下

故事從0.1+0.2說起

0.1+0.2是否等于0.3呢?

這是一個(gè)前端人耳熟能詳?shù)墓适拢恳粋€(gè)初入前端世界的人,應(yīng)該都會(huì)被它來一次靈魂拷問。它的出現(xiàn),似乎打破了人們以往對(duì)于代碼世界“執(zhí)行嚴(yán)謹(jǐn)、一絲不茍”的刻板印象。然而,這看起來“不夠嚴(yán)謹(jǐn)”的形成原因,卻正是因?yàn)?code>底層代碼執(zhí)行的足夠嚴(yán)謹(jǐn)!

在初入前端世界的時(shí)候,有那么一瞬,我甚至在想難道是底層對(duì)于0.10.2有一種特殊的感情?

然而事實(shí)并非如此,能被底層這種龐然大物看上并針對(duì)的,當(dāng)然不會(huì)只有0.10.2這兩個(gè)看起來平平無奇的數(shù)字,而是包含了這兩個(gè)數(shù)字在內(nèi)的一批特殊存在。

如下圖所示:

當(dāng)然,除上述圖片內(nèi)的數(shù)字之外,還有更多的其他數(shù)字也在這反常理的隊(duì)列之內(nèi)。

然而,這篇文章我們并不是來深入討論這些特殊的數(shù)字在進(jìn)行數(shù)學(xué)計(jì)算時(shí),與底層究竟產(chǎn)生了什么樣的恩怨糾葛。我們只需要簡(jiǎn)單知道:在計(jì)算機(jī)世界中,所有信息最后都是以二進(jìn)制存儲(chǔ)的,可是數(shù)字中的小數(shù)部分在按照一定規(guī)則轉(zhuǎn)換為二進(jìn)制時(shí),有些數(shù)字會(huì)產(chǎn)生無限循環(huán)的現(xiàn)象,但計(jì)算機(jī)精度位數(shù)是有限的,所以對(duì)超出位數(shù)的部分做了四舍五入的計(jì)算,因此造成了精度的丟失。

本文僅僅針對(duì)以上現(xiàn)象,結(jié)合日常開發(fā)的實(shí)踐,討論一些解決問題的方法。

初步解決

既然是因?yàn)樾?shù)部分在轉(zhuǎn)換二進(jìn)制時(shí)做了四舍五入的處理,那么計(jì)算時(shí)先將小數(shù)轉(zhuǎn)為整數(shù)再計(jì)算是不是就可以了?

依據(jù)上面的思想,在javascript中進(jìn)行小數(shù)計(jì)算,通常會(huì)采用放大倍數(shù)取整之后再計(jì)算,得出結(jié)果之后再縮小還原的技術(shù)方案。

例:計(jì)算0.1+0.2,通常會(huì)將0.1和0.2放大10倍,相加之后再縮小10倍

代碼如下:

  /**
   * 已知:a為0.1,b為0.2
   * 求:a與b的和
   * */
  const a = 0.1;
  const b = 0.2;
  const result = ((a * 10) + (b * 10)) / 10;
  console.log(result) // 0.3

如上,針對(duì)已知小數(shù)位數(shù)的數(shù)字,我們可以直接采用放大相應(yīng)倍數(shù)取整,然后再計(jì)算的方式來規(guī)避小數(shù)計(jì)算的精度問題。

可是在實(shí)際的業(yè)務(wù)開發(fā)中,對(duì)于需要進(jìn)行計(jì)算處理的數(shù)字,我們往往無法預(yù)先獲知數(shù)字包含的小數(shù)位數(shù)。對(duì)于此種情況,我們便需要先確定小數(shù)位數(shù),然后確定放大倍數(shù),再進(jìn)行計(jì)算。

代碼如下:

  /**
   * 已知:a,b為兩個(gè)精度隨機(jī)的小數(shù)
   * 求:a與b的和
  */

  // 生成精度隨機(jī)的小數(shù)
  const getNumber = () => {
    const len = Math.random() * 10;
    const num = Number((Math.random() * 10).toFixed(len))
    return num
  }

  // 計(jì)算放大倍數(shù)
  const getPower = (a, b) => {
    // 獲取a,b小數(shù)位長(zhǎng)度,如沒有小數(shù)位則默認(rèn)值為0
    const aLen = a.toString().split(".")[1]?.length || 0;
    const bLen = b.toString().split(".")[1]?.length || 0;
    // 獲取最大長(zhǎng)度
    const len = Math.max(aLen, bLen);
    // 計(jì)算返回放大倍數(shù)
    return Math.pow(10, len)
  }

  const a = getNumber();
  const b = getNumber();
  const power = getPower(a, b);

  const result = ((a * power) + (b * power)) / power;

因?yàn)橐陨洗a中,ab皆由getNumber函數(shù)隨機(jī)生成,為了便于觀察,我們添加log后,在瀏覽器中運(yùn)行代碼。

如下圖所示:

觀察可知,計(jì)算結(jié)果正確。以上,我們通過使用getPower函數(shù)確定放大的倍數(shù),然后進(jìn)行計(jì)算。這也是目前大部分同學(xué)解決小數(shù)計(jì)算精度問題的主要方式。

然而,故事到這里就結(jié)束了嗎?

當(dāng)然不是!

以上方式雖然解決了一些精度問題,但是并沒有解決所有的精度問題。在這個(gè)特殊的小數(shù)群體中,并不是所有的小數(shù)都可以通過放大倍數(shù)來取整的!

如下圖所示:

因此先放大再計(jì)算也并不是十分可靠,如下圖所示:

大膽取舍

Number.EPSILON

我們已經(jīng)知道,精度的誤差是由于底層在計(jì)算時(shí)做了一些四舍五入造成的,因此我們分析后可以斷定被舍棄的部分一定是小于可以表示的最小浮點(diǎn)數(shù)的。

例如:有數(shù)字 a1.234,對(duì) a 做保留兩位小數(shù)的處理后,得到數(shù)字 b ,b的值為1.23。則上述操作中舍棄的部分 0.004,一定小于保留精度 0.01

基于以上分析,我們有理由相信:在 javascript 中當(dāng)兩個(gè)數(shù)字之間的差值小于可以表示的最小浮點(diǎn)數(shù),那么我們就認(rèn)為這兩個(gè)數(shù)字相等。

可是,最小的浮點(diǎn)數(shù)該如何獲取呢?

javascript 為我們提供了這樣一個(gè)屬性:Number.EPSILON 靜態(tài)數(shù)據(jù)屬性,表示 1 與大于 1 的最小浮點(diǎn)數(shù)之間的差值。

詳細(xì)介紹可查看MDN。

我們對(duì)之前的代碼做一些優(yōu)化,在放大一定倍數(shù)之后,做差值比較,確認(rèn)最終結(jié)果。

代碼如下:

const getPower = (a, b, c) => {
  // 獲取a,b小數(shù)位長(zhǎng)度,如沒有小數(shù)位則默認(rèn)值為0
  const aLen = a.toString().split(".")[1]?.length || 0;
  const bLen = b.toString().split(".")[1]?.length || 0;
  const cLen = c.toString().split(".")[1]?.length || 0;
  // 獲取最大長(zhǎng)度
  const len = Math.max(aLen, bLen, cLen);
  // 計(jì)算返回放大倍數(shù)
  return Math.pow(10, len)
}
// 差值比價(jià)
const compare = (n) => {
  const result = Math.round(n);

  // 如差值小于 Number.EPSILON 則認(rèn)為和取整之后的數(shù)字相等
  return n - result < Number.EPSILON ? result : n;
}

var a = 19.9;
var b = 4788.4;
var c = 0.01;
var power = getPower(a, b, c);

const result = (compare((a * power)) + compare((b * power)) + compare((c * power))) / power
console.log(result)

在瀏覽器運(yùn)行代碼,可知計(jì)算正確。

如下圖所示:

Math.round

理論上講,一個(gè)兩位小數(shù)乘 100 后一定會(huì)得到一個(gè)整數(shù),一個(gè)三位小數(shù)乘 1000 以后一定也會(huì)得到一個(gè)整數(shù)。同理可知,一個(gè) n 位小數(shù)乘(10^n)后,一定可以得到一個(gè)整數(shù)!

雖然在計(jì)算機(jī)世界中小數(shù)計(jì)算有些誤差,但通過上述代碼我們知道,這個(gè)誤差小到幾乎可以忽略,那么我們是不是可以大膽一點(diǎn),放大之后無需比較,直接四舍五入!

我們修改以上代碼,舍棄compare函數(shù)。

代碼如下:

const getPower = (a, b, c) => {
  // 獲取a,b小數(shù)位長(zhǎng)度,如沒有小數(shù)位則默認(rèn)值為0
  const aLen = a.toString().split(".")[1]?.length || 0;
  const bLen = b.toString().split(".")[1]?.length || 0;
  const cLen = c.toString().split(".")[1]?.length || 0;
  // 獲取最大長(zhǎng)度
  const len = Math.max(aLen, bLen, cLen);
  // 計(jì)算返回放大倍數(shù)
  return Math.pow(10, len)
}

var a = 19.9;
var b = 4788.4;
var c = 0.01;
var power = getPower(a, b, c);

const result = (Math.round((a * power)) + Math.round((b * power)) + Math.round((c * power))) / power
console.log(result)

在瀏覽器中運(yùn)行后發(fā)現(xiàn),結(jié)果依然正確。

如下圖所示:

封裝完善

基于以上推導(dǎo),我們可以封裝一個(gè)簡(jiǎn)易的計(jì)算函數(shù)。

代碼如下:

function compute(type, ...args) {
  // 計(jì)算放大倍數(shù)
  const getPower = (numbers) => {
    const lens = numbers.map(num => num.toString().split(".")[1]?.length || 0);
    // 獲取最大長(zhǎng)度
    const len = Math.max(...lens);
    // 計(jì)算返回放大倍數(shù)
    return Math.pow(10, len)
  }

  // 獲取放大倍數(shù)
  const power = getPower(args);

  // 獲取放大后的值
  const newNumbers = args.map(num => Math.round(num * power));

  // 計(jì)算結(jié)果
  let result = 0;
  switch (type) {
    case "+":
      result = newNumbers.reduce((preNumber, nextNumber) => preNumber + nextNumber, result) / power;
      break;
    case "-":
      result = newNumbers.reduce((preNumber, nextNumber) => preNumber - nextNumber) / power;
      break;
    case "*":
      result = newNumbers.reduce((preNumber, nextNumber) => preNumber * nextNumber) / (power ** newNumbers.length);
      break;
    case "/":
      result = newNumbers.reduce((preNumber, nextNumber) => preNumber / nextNumber);
      break;
  }

  return {
    result,
    next(nextType, ...nextArgs) {
      return compute(nextType, result, ...nextArgs);
    }
  }
}

// 驗(yàn)證
const arr = [0.1, 0.2, 29.6]
const a = compute('+', ...arr);
const b = a.next('-', 4, 2, 4);
const c = b.next('*', 100);
const d = c.next('+', 2798.4);
const e = d.next('*', 100);
const f = e.next('/', 1000);
const r = compute('+', ...arr).next('-', 4, 2, 4).next('*', 100).next('+', 2798.4).next('*', 100).next('/', 1000);

console.log('a: ', a.result) // a:  29.9
console.log('b: ', b.result) // b:  19.9
console.log('c: ', c.result) // c:  1990
console.log('d: ', d.result) // d:  4788.4
console.log('e: ', e.result) // e:  478840
console.log('f: ', f.result) // f:  478.84
console.log('r: ', r.result) // f:  478.84

經(jīng)簡(jiǎn)單測(cè)試后,可知compute函數(shù)已實(shí)現(xiàn)基本的四則運(yùn)算,且可以鏈?zhǔn)秸{(diào)用。

結(jié)語(yǔ)

若有錯(cuò)誤,請(qǐng)務(wù)必給予指正。 謝謝!

以上就是JS中數(shù)學(xué)計(jì)算精度問題的解決方案的詳細(xì)內(nèi)容,更多關(guān)于JS數(shù)學(xué)計(jì)算精度問題的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

祁连县| 汤原县| 洱源县| 松滋市| 赤水市| 安顺市| 富顺县| 雷州市| 吉安县| 来安县| 祁阳县| 漳平市| 望奎县| 孝义市| 岗巴县| 崇左市| 嘉兴市| 旌德县| 客服| 大渡口区| 工布江达县| 嘉善县| 专栏| 云南省| 井冈山市| 苏尼特右旗| 景洪市| 沁阳市| 伽师县| 温州市| 毕节市| 滦南县| 辰溪县| 固原市| 陈巴尔虎旗| 沾益县| 博湖县| 吴江市| 南溪县| 普兰县| 顺义区|