1 浮点数运算与 IEEE 754 标准

在 JavaScript 中,执行 0.1+0.2,得到的结果却是 0.30000000000000004。这就不得不提到 IEEE 754 标准。

IEEE二进制浮点数算术标准(IEEE 754)定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)、一些特殊数值(无穷(Inf)与非数值(NaN))、以及这些数值的“浮点数运算符”,它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。该标准为许多CPU与浮点运算器所采用。

ECMAScript® 语言规范中的 Number 类型遵循 IEEE 754 标准,使用64位固定长度来表示。

IEEE754 规定:

单精度浮点数字长 32 位,尾数长度 23(即小数部分),指数长度 8,指数偏移量 127;

双精度浮点数字长 64 位,尾数长度 52(即小数部分),指数长度 11,指数偏移量 1023;

约定小数点左边隐含有一位 1(在二进制,第一个有效数字必定是1,所以不用存储)。所以上述单精度尾数长度实际为24,双精度尾数长度实际为53。

对于无限循环小数和无理数来说,超过 23(单精度)/52(双精度) 位的小数部分必然会出现溢出而产生精度丢失。

十进制的浮点数运算,都会先转换为二进制,再执行二进制的四则运算。但有一些浮点数在转化为二进制时,会出现无限循环 。比如十进制的 0.1 与 0.2 转换为二进制,会得到如下结果:

0.1 => 0.0001 1001 1001 1001…(无限循环)

0.2 => 0.0011 0011 0011 0011…(无限循环)

任何遵循 IEEE 754 标准的编程语言都会如此。如果你有兴趣了解具体都哪些编程语言,可参考这个网站:

2 确保 JavaScript 浮点数运算精度的方法

2.1 转换为整数计算

先将需要计算的数字乘以 10 的 n 次幂,换成计算机能够精确识别的整数,等计算完毕再除以 10 的 n 次幂以还原精度。这是较为常见的处理精度差异的方法。

以下为实现代码示例:

function toNonExponential(num) {

num = Number(num);

const strNum = String(num);

if (strNum.indexOf('e') === -1) return strNum;

const m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);

return num.toFixed(Math.max(0, (m[1] || '').length - Number(m[2])));

}

function getDecimalLen(num) {

try {

return toNonExponential(num).split('.')[1].length;

} catch (f) {

return 0;

}

}

/** 加法 */

function add(a, b) {

const n1 = getDecimalLen(a);

const n2 = getDecimalLen(b);

const n = Math.pow(10, Math.max(n1, n2));

return (mul(a, n) + mul(b, n)) / n;

}

/** 减法 */

function sub(a, b) {

return add(a, -b);

}

/** 乘法 */

function mul(a, b) {

const decimalLen = getDecimalLen(a) + getDecimalLen(b);

const e = Math.pow(10, decimalLen);

const aa = Number(toNonExponential(a).replace('.', ''));

const bb = Number(toNonExponential(b).replace('.', ''));

return (aa * bb) / e;

}

/** 除法 */

function div(a, b) {

const decimalLen = getDecimalLen(b) - getDecimalLen(a);

const e = Math.pow(10, decimalLen);

const aa = Number(toNonExponential(a).replace('.', ''));

const bb = Number(toNonExponential(b).replace('.', ''));

return e === 1 ? aa / bb : mul(aa / bb, e);

}

验证:

console.log('0.1 + 0.2 =', 0.1 + 0.2); // 0.30000000000000004

console.log('add(0.1, 0.2) =', add(0.1, 0.2)); // 0.3

console.log('0.3 - 0.1) =', 0.3 - 0.1); // 0.19999999999999998

console.log('sub(0.3, 0.1) =', sub(0.3, 0.1)); // 0.2

console.log('0.1 * 0.2) =', 0.1 * 0.2); // 0.020000000000000004

console.log('mul(0.1, 0.2) =', mul(0.1, 0.2)); // 0.02

console.log('19.9 * 100) =', 19.9 * 100); // 1989.9999999999998

console.log('mul(19.9, 100) =', mul(19.9, 100)); // 1990

console.log('0.02 / 0.2) =', 0.02 / 0.2); // 0.09999999999999999

console.log('div(0.02, 0.2) =', div(0.02, 0.2)); // 0.1

相关实现参考:

2.2 对计算结果执行 toFixed

由于精度误差都在最后一位小数,只要精确计算结果的小数位未溢出,那么执行 toFixed(decimal)即可还原精确的精度。其中 decimal 为精确计算结果的小数位。

例如加、减、乘法实现示例(除法可用第一种方式实现,见前文示例代码):

/** 加法 */

function add(a, b) {

const n1 = getDecimalLen(a);

const n2 = getDecimalLen(b);

const n = Math.max(n1, n2);

return parseFloat((a + b).toFixed(n));

}

/** 减法 */

function sub(a, b) {

return add(a, -b);

}

/** 乘法 */

function mul(a, b) {

const decimalLen = getDecimalLen(a) + getDecimalLen(b);

return parseFloat((a * b).toFixed(decimalLen));

}

同样可采用前文代码执行验证。

2.3 按长度分段计算

以上方法可以解决十进制小数和二进制小数转换计算带来的误差问题,但不能提高运算的精度,也不能突破 JavaScript 数字表示的范围。对于常见的前端计算来说这已经足够了。对于涉及大量数据汇总类的大数计算,应当由后端接口计算并以字符串方式返回以供展示。

不过采用按长度分段计算、以字符串方式展示的思路,也可以突破这些限制。此种方式需要考虑的细节场景相对较为复杂,常见的流行开源库多采用该方式实现。例如开源库 decimal.js 即此类方案的典范:

decimal.js 是一个非常成熟的 JavaScript 小数计算处理库,支持非常多的数学计算方法。如对其细节有兴趣,可参阅一下其源码实现。另外还有 big.js、mathjs、bignumber.js 等流行开源库可选用。

2.4 总结

一般来说,简单的常见四则运算处理,前两种方式就足够了,实现简单,代码量也少。

如果有复杂数学计算需求,可采用 decimal.js 等开源库。不过由于这些库包含功能比较多,一般压缩后仍有数十KB大小。如果只是简单的四则运算,不妨采用前两种思路自行实现。

最后安利一下这个 asmd-calc 四则运算 js 库,其核心实现不足百行代码,为第一种方法(转为整数计算)的实现。该库为个人在工作过程中实践经验的总结实现,历经多个涉及大量简单四则计算的金融类项目。如有简单计算需求可参考使用,也欢迎提出改进意见。

3 相关参考

js 单精度浮点数转10进制_确保前端 JavaScript 浮点数精度的四则运算方法相关推荐

  1. c# .net 16进制转换10进制

    业务调用 #region C#十六进制字符串转十进制 { Console.WriteLine("-----------十六进制字符串转十进制---------");//H:十六进制 ...

  2. 2进制 , 8进制 , 10进制 , 16进制 , 介绍 及 相互转换 及 快速转换

    为什么要使用进制数 数据在计算机中的表示,最终以二进制的形式存在 , 就是各种 <黑客帝国>电影中那些 0101010- 的数字 ; 我们操作计算机 , 实际 就是 使用 程序 和 软件 ...

  3. C++单、双精度浮点数16进制转10进制原理及代码

    一.浮点数16进制转10进制原理 浮点数有两种:单精度float(4字节) 和 双精度double(8字节). 1.单精度结构表 符号位 Sign (S) 指数部分 Exponent (E) 尾数部分 ...

  4. python十六进制转十进制_使用Python 16进制转10进制

    原博文 2019-05-07 15:22 − """ 16进制转10进制 """ # str="A5 42 D2 00 4A 00 ...

  5. java 十六进制转十进制_「16进制转10进制」Java:十六进制转换成十进制 - seo实验室...

    16进制转10进制 问题及代码: /* *问题描述 从键盘输入一个不超过8位的正的十六进制数字符串,将它转换为正的十进制数后输出. 注:十六进制数中的10~15分别用大写的英文字母A.B.C.D.E. ...

  6. js 10进制 16进制互相转换

    10进制转16进制: var ab = 32; console.log(ab.toString(16)) // 20 16进制转10进制: var aa = 20 console.log(parseI ...

  7. Node js 10进制转16进制 固定4个字节大小的转换

    需求: 要求把10进制转换成固定4个字节大小的16进制数. 例子: 1000  => 0x000003e8 11223344 => 0x00ab4130 function toHex(nu ...

  8. mysql将10进制转为16进制的函数_用SQL实现某字段十进制转十六进制

    展开全部 利用SQLSERVER中的varbinary来间接实现. 16进制字符串转10进制bigint(e69da5e6ba9032313133353236313431303231363533313 ...

  9. java 10进制转64进制_十进制与64进制互相转换算法

    实现代码如下: /** * */ package com.M.controller.test; import java.util.Stack; /** * * @author online zuozu ...

最新文章

  1. Swift中关于元组的某些特性
  2. 消息中间件之JMS实践(ActiveMQ)
  3. 再不用担心DataRow类型转换和空值了(使用扩展方法解决高频问题)
  4. MYSQL从节点延迟问题原因及解决
  5. oracle 存过调试 stepinto stepover stepout
  6. 打开pjsip2.1版本的视频支持
  7. AI学习笔记(二)图像与视频
  8. 阿里云短信sdk的懒人用法
  9. 知网上下载硕博论文为PDF格式的方法
  10. 第十二章 非编码RNA与复杂疾病
  11. 进程管理软件SysCheck使用指南
  12. php 英文小写变大写,PHP_PHP英文字母大小写转换函数小结,每个单词的首字母转换为大写 - phpStudy...
  13. linux-rootfs根文件系统构建
  14. 深度操作系统deepin 20.9 正式发布!
  15. 趣图:菜鸟多线程 vs 老鸟多线程
  16. Vue项目实战 —— 哔哩哔哩移动端开发—— 第一篇
  17. 强行替换exe图标的方法
  18. 07_plantform平台总线
  19. php esc p 打印,ESC/P 打印指令使用,3种票据打印方法(转)
  20. Android系统韦根调试从驱动到应用(二)

热门文章

  1. java字符串拼接_Java 8中字符串拼接新姿势:StringJoiner
  2. 使用计算机教学的意义,信息技术在教学中的作用
  3. 【OJ二分06】月度开销
  4. 6.边缘检测:梯度——计算梯度Matlab实战_5
  5. linux读取扇区内容,linux-device-driver
  6. bzoj1047 [HAOI2007]理想的正方形 单调队列
  7. 2017.8.16 喵星球上的点名 思考记录
  8. 汇编怎么从内存地址写入连续的数字_汇编语言 第一章 基础知识
  9. 如何快速开发一个支持高效、高并发的分布式ID生成器(一)
  10. php 7 class 初始化 销毁_在 PHP 中使用和管理 Session