JavaScript中小数点精度丢失的原因以及解决方法
作者:你脸上长麻子
一、为什么会出现计算精度丢失的问题?
JavaScript中存在小数点精度丢失的问题是由于其使用的浮点数表示方式。JavaScript采用的是双精度浮点数表示法,也称为IEEE 754标准,它使用64位来表示一个数字,其中52位用于表示有效数字,而其他位用于表示符号、指数和特殊情况。
由于使用有限的位数来表示无限的小数,JavaScript无法准确地表示某些小数。其中一个典型的示例是0.1,它在二进制中是一个无限循环的小数。当我们将0.1这样的小数转换为二进制进行存储时,存在近似表示的误差,因此在进行计算时会出现精度丢失的问题。
例如,执行以下计算:
0.1 + 0.2
预期的结果应该是0.3,但在JavaScript中实际得到的结果是0.30000000000000004。这是因为0.1和0.2无法以精确的二进制形式表示,导致小数点精度丢失。
这种精度丢失是浮点数表示法的固有特性,并不仅限于JavaScript,其他编程语言也可能面临类似的问题。因此,在进行精确计算时,特别是涉及到货币、金融等方面的计算,应使用其它精确计算的方法,例如使用整数进行运算或使用专门的精确计算库。
总而言之,小数点精度丢失的问题是由于JavaScript使用的浮点数表示法的特性所致,需要在开发中注意,并考虑使用其他方法或工具来解决精度问题。
二、代码示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script> var floatObj = function () { // 判断传入的值-是否为整数 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: 1, num: 0 } // 第一种情况:是整数 if (isInteger(floatNum)) { // 把整数给 ret中的 num ret.num = floatNum return ret // 最后返回 ret } // 第二种情况-不是整数, var strfi = floatNum + '' // 转为字符串 "0.1" var dotPos = strfi.indexOf('.') // 查询小数点 var len = strfi.substr(dotPos + 1).length; // 获取小数点后的长度 var times = Math.pow(10, len) // 放大多少倍 var intNum = Number(floatNum.toString().replace('.', '')) // 返回 转为字符串 截取掉小数点 最后转为数字(整数) // 把获取到的倍数和整数存入对象中 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 n1 = o1.num var n2 = o2.num // 提取倍数 var t1 = o1.times var t2 = o2.times // 获取最大倍数 var max = t1 > t2 ? t1 : t2 var result = null // switch (op) { case 'add': if (t1 === t2) { // 两个小数位数相同 result = n1 + n2 // } else if (t1 > t2) { // o1 小数位 大于 o2 result = n1 + n2 * (t1 / t2) } else { // o1 小数位 小于 o2 result = n1 * (t2 / t1) + n2 } return result / max case 'subtract': if (t1 === t2) { result = n1 - n2 } else if (t1 > t2) { result = n1 - n2 * (t1 / t2) } else { result = n1 * (t2 / t1) - n2 } return result / max case 'multiply': result = (n1 * n2) / (t1 * t2) return result case 'divide': result = (n1 / n2) * (t2 / t1) return result } } // 加减乘除的四个接口 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') } return { add: add, subtract: subtract, multiply: multiply, divide: divide } }(); // console.log(floatObj.add(0.5, 0.2)) console.log(floatObj.add(0.12, 0.3)) </script> </head> <body> </body> </html>
三、解决思路
解决方法:
1.首先封装一个函数,这个函数是用来判断单价是否为一个整数。
2.然后在封装第二个函数,这个函数的作用是返回整数和倍数的。
同时声明一个对象ret来保存这个倍数和整数,
第一种情况就是单价传过来时就是整数,那么他的倍数也就为1, 所以我们可以直接存储到声明的对象中
第二种情况就是当单价传过来时是浮点数,这时候我们就要做处理,
(1)、将传递过来的参数转为字符串(隐式转换),
(2)、使用indexOf找到小数点'.',
(3)、使用substr字符串方法截取小数点后的长度length,
(4)、声明一个为倍数的变量Time,同时使用Math.pow(10, 上一步截取的长度), 这个time就是倍数,小数点后每多一位就会为10×长度的次方,再将浮点数转换为整数(tostring转为字符, 使用replace将小数点替换为空,再用Number将字符串转为数字)
最后将获取到的倍数和整数存入对象中即可
3.最后实现加减乘除运算,确保不丢失精度
主要思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
再次封装一个为实现运算的函数这个函数接受三个参数(运算数1,运算数2, '加减乘除四个方法的函数'),
声明n1, n2, 分别为单价1, 单价2,
声明t1, t2.为单价1的倍数和单价2的倍数
再获取最大倍数max(使用三元运算符),
使用switch判断条件就是('加减乘除四个方法的函数')搭配Case判断
加法: 三种状况
1.t1和t2相同的情况下,就是两个小数的位数相同情况下直接返回n1 + n2 就是整数相加
2.t1大于t2的情况下 第一个小数位数大于第二位返回, n1 + n2 * (t1 / t2):
3.t1小于t2的情况下 第一个小数位数小于第二位返回, n1 * (t2 / t1) + n2
最后无论走得是哪一种情况结果都要除以max(最大倍数)
减法: 三种状况
1.t1和t2相同的情况下,就是两个小数的位数相同情况下直接返回n1 - n2 就是整数相减
2.t1大于t2的情况下 第一个小数位数大于第二位返回, n1 - n2 * (t1 / t2)
3.t1小于t2的情况下 第一个小数位数小于第二位返回, n1 * (t2 / t1) - n2
最后无论走得是哪一种情况结果都要除以max(最大倍数)
乘法: 判断状态时候结果的是(n1 × n2)/ (t1 × t2)
除法: 判断状态时候结果的是(n1 × n2)/ (t1 × t2)
--- 最后进行加减乘除进行封装,return这四个函数-- -
四、解决的问题
在 JavaScript 中处理小数点精度可以带来以下几个好处:
避免精度丢失:JavaScript 使用 IEEE 754 浮点数标准来表示数字,但由于浮点数存储的是二进制近似值,会导致一些小数无法被准确表示,从而造成精度丢失。通过处理小数点精度,可以减少这种精度丢失的情况,确保计算结果的准确性。
精确计算金融数据:在金融领域,小数点精度非常重要。处理货币金额、计算利率和利息等都需要保持精确的小数点计算,以避免数据错误和损失。
避免舍入误差:当进行多个浮点数计算时,由于每次计算都可能存在一定的舍入误差,累积计算结果可能会与预期的结果有所偏差。通过处理小数点精度,可以减少舍入误差的影响,提高计算的准确性。
增强数据可视化:在图表和可视化数据中,小数点精度可以提供更精确的数据展示。例如,在绘制股票价格曲线或者科学实验数据时,小数点精度可以使得数据更加准确和可信。
总的来说,处理小数点精度可以确保计算结果的准确性,尤其在对于金融数据和需要高精度计算的场景下,这种处理是非常重要的。
五、二进制存储原理
在JS中不区分整数和小数,因为JS中天生浮点数(双精度),在计算机存储中,双精度的实际存储位数是52位,由于二进制中只有 0 和 1,但52位有时并不能准确的表达小数点后面的数字,在十进制中有四舍五入,在二进制中存在0舍1入,所以当52位无法准确的表达出一个小数时,就会产生补位动作,数值偏差就在这时产生了,这是造成计算精度问题的原因。
总结
到此这篇关于JavaScript中小数点精度丢失的原因以及解决方法的文章就介绍到这了,更多相关JS小数点精度丢失内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!