我想用|我想用 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 实现 0.1 + 0.2 输出 0.3】然后又被问了,如果我是两位小数呢?三位呢? 让我说你就没悟性啊!同样道理你加就完事了。然后他急了,他说三十位呢?
这样问题就变了,变成了 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 了,加上计算属性,咔咔好用。
我想用|我想用 JS 实现 0.1 + 0.2 输出 0.3
文章图片

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

    推荐阅读