我想用JS实现0.1 + 0.2 输出0.3

简介: 我想用JS实现0.1 + 0.2 输出 0.3

起因


昨天被人问到了一个问题:


因为 JS 精度问题 0.1 + 0.2 == 0.30000000000000004 ,可以不可以得出一个正确的值。 0.1 + 0.2 == 0.3

这简单,变成整数,然后再除回去。或者取整,保留小数。

  • ((0.1 + 0.2) * 10).toFixed()/10
  • (0.1 * 10 + 0.2 * 10)/10


然后又被问了,如果我是两位小数呢?三位呢? 让我说你就没悟性啊!同样道理你加就完事了。然后他急了,他说三十位呢?


这样问题就变了,变成了 JS 是否能存下这样一个数字。


我一想,这不就是大数计算吗,我刷过。为了简单我找了个库 bignumber 来实现这部分逻辑


new BigNumber(0.1).plus(0.2) 简单的一批。


但是这里我自己发现了问题:需要按照特定的写法,这不合理。我又想到,我之前刷过计算器,里面给你输入一个 '1+2+3*4' 然后让你输出计算结果(虽然有偷懒的 eval 方案,但是架不住有题解呀)

说整就整,一查 :


  • 题一,只支持加减


  • 题二,支持括号,不支持乘除


  • 题三,逐渐变态。根本就不是数学。是一套自己的逻辑。


  • 题四,加密,需要开会员。


没办法,白嫖失败(tongxin),自己写又太费时间,看看题解 get 了一个关键词逆波兰式,可以处理计算优先级。


表达式 转换为 逆波兰式


这就不得不吐槽,网上的垃圾资源了


  • 居然不支持小数
  • 居然只支持个位
  • 输出结果也是有问题的
  • 居然搜索结果在第一屏
  • 居然前一页都是同一篇文章


好在整数计算是没问题的,自己动手丰衣足食。我们把获取到数字哪里在做一个 while 以保证完整。


function calculator(str) {
        let n = 0, charStack = [], numStack = [], reducerStr =  [], leftIndex = -1
        const op = {
            '+' : 1,
            '-' : 1,
            '*' : 2,
            '/' : 2,
            '(' : 3,
            ')' : 3
        }
        while(n < str.length) {
            const byte = str[n]
            // 数字
            // if(/\d/.test(byte)) {
            if(/[\d\.]+/.test(byte)) {
                // reducerStr.push(byte)
                let result = byte;
                let _str = str[n+1]
                while(/[\d\.]+/.test(_str)){
                    result+=_str;
                    n++;
                    _str = str[n+1]
                }
                reducerStr.push(result)
            } else if(/\(|\)/.test(byte)) {
                // 左括号入栈
                if(byte === '(') {
                    charStack.push(byte)
                    leftIndex = n
                    // console.log('左括号', byte)
                // 右括号出栈
                } else {
                    let nowChar = charStack.pop()
                    while(nowChar && nowChar !== '(') {
                    reducerStr.push(nowChar)
                    nowChar = charStack.pop()
                    }
                }
                // 符号
            } else {
                // 字符栈顶元素
                let nowChar = charStack[charStack.length - 1]
                while(nowChar && op[byte] < op[nowChar] && nowChar !== '(') {
                    charStack.pop()
                    reducerStr.push(nowChar)
                    nowChar = charStack[charStack.length - 1]
                }
                charStack.push(byte)
            }
            n++
        }
        while(charStack.length) {
            reducerStr.push(charStack.pop())
        }
        return reducerStr
    }

 

解析逆波兰式计算结果


这个也要吐槽,应该是个题解,他把小数取整了 凸。

把取整干掉。然后把计算位置换成我们的库。


var evalRPN = function(tokens) {
        const stack = [];
        const n = tokens.length;
        for (let i = 0; i < n; i++) {
            const token = tokens[i];
            if (isNumber(token)) {
                stack.push((token));
                // stack.push(parseInt(token));
            } else {
                const num2 = stack.pop();
                const num1 = stack.pop();
                if (token === '+') {
                    stack.push(new BigNumber(num1).plus(num2));
                } else if (token === '-') {
                    stack.push(new BigNumber(num1).minus(num2));
                } else if (token === '*') {
                    stack.push(new BigNumber(num1).times(num2));
                } else if (token === '/') {
                    stack.push(new BigNumber(num1).dividedBy(num2));
                    // stack.push(num1 / num2 > 0 ? Math.floor(num1 / num2) : Math.ceil(num1 / num2));
                }
            }
        }
        return stack.pop();
    };
    const isNumber = (token) => {
        return !('+' === token || '-' === token || '*' === token || '/' === token );
    }


便捷测试


这里当然是上 vue 了,加上计算属性,咔咔好用。


bVcWUPz.webp.jpg


相关资源



  1. 前端 BUG 录 - 科学计数法是什么? 这里有一些精度相关的资料,可以点进去看看
  2. 测试地址:http://jsrun.net/9f9Kp/edit


bVcWXSN.webp.jpg


再补个精度相关的内容。

Math.abs(0.1+0.2 - 0.3) < Number.EPSILON

Math.abs(0.1+0.2 - 0.3000000000000001) < Number.EPSILON

Math.abs(0.1+0.2 - 0.300000000000001) < Number.EPSILON


EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16,或者 2-52。

相关文章
|
JavaScript 前端开发
javascript深拷贝和浅拷贝以及实现方法(推荐)
javascript深拷贝和浅拷贝以及实现方法(推荐)
600 0
javascript深拷贝和浅拷贝以及实现方法(推荐)
|
JavaScript 算法 前端开发
【前端算法】JS实现数字千分位格式化
JS实现数字千分位格式化的几种思路,以及它们之间的性能比较
343 1
|
存储 前端开发 算法
一行代码解决LeetCode实现 strStr()使用JavaScript解题|前端学算法
一行代码解决LeetCode实现 strStr()使用JavaScript解题|前端学算法
163 0
一行代码解决LeetCode实现 strStr()使用JavaScript解题|前端学算法
|
存储 机器学习/深度学习 JavaScript
JS 你最少用几行代码实现深拷贝?
JS 你最少用几行代码实现深拷贝?
JS 你最少用几行代码实现深拷贝?
|
JavaScript 前端开发 算法
JavaScript实现一段时间之后关闭广告
简介:通过JavaScript实现在一段时间之后,广告消失。
131 0
JavaScript实现一段时间之后关闭广告
|
JavaScript 前端开发 算法
JS实现鼠标悬停变色
本文实现的是利用JS实现当鼠标悬停在表格上的时候,表格发生变色。 CSS渲染 JS逻辑 `
220 0
JS实现鼠标悬停变色
|
JavaScript 前端开发 数据安全/隐私保护
JS实现关闭图片窗口
通过事件的绑定来实现,关闭二维码的效果。
159 0
JS实现关闭图片窗口
|
前端开发 JavaScript Windows
js实现body背景图自动扩缩 光靠css几乎无法实现这样的效果
js实现body背景图自动扩缩 光靠css几乎无法实现这样的效果
196 0
js实现body背景图自动扩缩 光靠css几乎无法实现这样的效果
|
存储 JavaScript
js实现多选、全选、反选、取消选择(篇一)
js实现多选、全选、反选、取消选择(篇一)
389 0
js实现多选、全选、反选、取消选择(篇一)
|
JavaScript 前端开发
利用JavaScript实现二级联动
利用JavaScript实现二级联动 要实现JavaScript二级联动效果,首先要确定需要哪些技术: 二维数组 for in循环 new Option(text,value,true,true) add(option,null) onchange() 表单事件 HTML代码: &lt;!-- &lt;input type=&quot;text&quot; id=&quot;text&quot;&gt; --&gt; 请选择省份: &lt;select name=&quot;&quot; id=&quot;provinces&quot;&gt; &lt;!-- &lt;option value=&quot;江苏省&quot;&gt;江苏省&lt;/option&gt;