zoukankan      html  css  js  c++  java
  • 处理精度丢之-如何解决

    通过上篇我们了解到计算机是如何存储浮点数,那精度丢失是在哪产生的?


    拿0.1 + 0.2举例:

    0.1

    转二进制后:0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 01(转化后是以0011无限循环,二进制为满一进一,所以末尾为01)

    0.2:

    转二进制后:0.0 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 10

    相加:0.0 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 11

    工具转换为十进制: 0.30000000000000004

    其实在四则运算中,浮点数转换成二进制后如果存在无限循环小数,都会有精度异常的情况

    那对于精度异常,该如何解决?

    1、展示类:错误数据转正

    如:strip(0.30000000000000004) ==> 0.3

    function strip(num, precision = 12) {
    return +parseFloat(num.toPrecision(precision))
    }

    tips: 此方法仅适用于页面展示,不适用参与计算。至于precision 为什么是12,很多相关资料说根据经验12基本能解决日常的精度丢失。


    2、计算类:相加、减、乘、除丢失精度解决方案

    如:0.1 + 35.41

    这里存在一个容易会踩的坑,我们知道浮点数的计算解决思路是转换为整数。那是不是就是直接将目标值各自乘以10的n次方转化为整数就可以。

    就像35.41, 我们想的是乘以100, 转化为整数应该就阔以了。事实是:35.41 * 100 ==》 3540.9999999999995,尴尬极了,所以我们需要通过转换成字符串格式进行化整。

    下面代码通过不借助第三方包解决精度丢失:

    // 精确加法
    function plus(num1, num2) {
      const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)))
      return (times(num1, baseNum) + times(num2, baseNum)) / baseNum
    }
    // 精确减法
    function minus(num1, num2, ...others) {
      const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)))
      return (times(num1, baseNum) - times(num2, baseNum)) / baseNum
    }
    // 精确乘法
    function times(num1, num2, ...others) {
      const num1Changed = float2Fixed(num1)
      const num2Changed = float2Fixed(num2)
      const baseNum = digitLength(num1) + digitLength(num2)
      const leftValue = num1Changed * num2Changed
      checkBoundary(leftValue)
      return leftValue / Math.pow(10, baseNum)
    }
    // 精确除法
    function divide(num1, num2, ...others) {
      const num1Changed = float2Fixed(num1)
      const num2Changed = float2Fixed(num2)
      checkBoundary(num1Changed)
      checkBoundary(num2Changed)
      return times((num1Changed / num2Changed), Math.pow(10, digitLength(num2) - digitLength(num1)))
    }
    // 检测数字是否越界,如果越界给出提示
    function checkBoundary(num) {
      if (_boundaryCheckingState)
        if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
          console.warn(`${num} 超出数字安全范围(${Number.MAX_SAFE_INTEGER},${Number.MIN_SAFE_INTEGER}),计算结果可能不准确`)
        }
      }
    }
    // 小数转整数
    function float2Fixed(num) {
      num = num || 0
      if (num.toString().indexOf('e') === -1) {
        return Number(num.toString().replace('.', ''))
      }
      const dLen = digitLength(num)
      return dLen > 0 ? strip(num * Math.pow(10, dLen)) : num
    }
    // 小数点后的字符串长度
    function digitLength(num) {
      num = num || 0
      const eSplit = num.toString().split(/[eE]/)
      const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0))
      return len > 0 ? len : 0
    }
    // 错误数据转正常
    function strip(num, precision = 12) {
      return +parseFloat(num.toPrecision(precision))
    }

    借助第三方引入npm包解决:

    1) 如Math.js、BigDecimal.js

    2) https://github.com/dt-fe/number-precision (介绍说大小只有1k,实用性强)

    大整数

    大整数丢失精度的原理和浮点数的原理是一致的。双精度的尾数位为11位,JS中能精准表示的最大整数为Math.pow(2, 53), 超过这个数的大整数将丢失精度。

    解决方案:BigDecimal.js提供方法解决,原理是将大整数当做字符串处理,缺点是损耗性能

    除了上面说的这些,js中还有一个toFixed方法,也存在丢失精度的问题

    tofixed方法

    对于浮点数,保留对应的位数我们首先想到的就是tofixed方法,然而某些值却不是我们想象的那样。

    如1.335.tofixed(2) ==> 1.33

    查阅相关资料,发现tofixed方法采用算法是一种叫什么银行家算法,并不是我们数学中的四舍五入。而且针对不同浏览器具体规则还不一致。你说好好的四舍五入不用,为啥得用这个银行家算法,搞不懂,也没查到对应的相关资料说明。

    抱怨归抱怨,遇到问题还是得解决。

    解决疑问:

    1)原生tofixed方法不对,就需要自己来构造一个我们想要的tofixed

         我写的有漏洞,暂时就不拿出来坑大家了,来日补上~

    2)假如项目中已经很多地方都使用tofixed,使用自己构造的方法,带来的是改动范围偏大。所以简洁的改动就只能重写原生方法

    Number.prototype.toFixed = function(n) {
      let newNum = 0
      if (!n) {
         newNum = Math.round(this)
      } else {
         newNum = Math.round(this * Math.pow(10, n)) / Math.pow(10, n)
      }
      return newNum + ''
    }
  • 相关阅读:
    sharepoint server 2010 打开网页非常慢
    sharepoint 2010 彻底删除用户
    Exchange2007设置网页OWA访问
    sharepoint 2007 网页内嵌打开pdf
    BAT+VBS、BAT+REG、BAT+HTML 混合编程
    Exchange2007安装后如何添加域账户邮箱
    Outlook2003无法登陆Exchange2007邮箱,提示outlook版本禁止
    Winsock Fix for Windows 7
    Silverlight 3 脱机安装
    WCF问题及解决方案
  • 原文地址:https://www.cnblogs.com/Tiboo/p/13837960.html
Copyright © 2011-2022 走看看