likes
comments
collection
share

Js精度问题:解决13572633524226 & 8796093022208的结果为0

作者站长头像
站长
· 阅读数 13

前言

大家好呀!我在罗杰手下练习两年半的实习船员hong,刚刚毕业三天非常兴奋,正准备在公司大展拳脚结果就掉到了一个又一个的大坑里。

Js精度问题:解决13572633524226 & 8796093022208的结果为0

今天基于业务需要,需要使用&(位与运算符)对两个数据进行比较,等到输出结果的时候大吃一惊。

console.log(13572633524226 & 8796093022208)
// 输出结果为0

是的,结果竟然为0。

出于严谨的心态,我使用了线上位运算符计算器,对结果重新计算,正确结果如下:

Js精度问题:解决13572633524226 & 8796093022208的结果为0

当时的想法非常简单,丢失精度或者js语言存在缺陷。

一.为什么用&

前一段时间有幸拜读了大佬们的文章《位运算和权限设计》,还有《面试官问我按钮级别权限怎么控制,我说v-if,面试官说再见》,受益匪浅,在此感谢,会在文章最后放出地址让更多人看到,整合了两位大佬共同的思路,我开始为公司SAAS系统实现按钮级别的鉴权工作,封装了自定义指令v-permission,具体代码如下:

import { Directive } from 'vue'
import store from '@/store'

export const permission: Directive = {
  mounted(el, binding) {
    const { value } = binding
    const userCode = store.state.pluginsState.appInfo.perm_code
    const userPermission = userCode ? userCode.split(",") : [] 
    const [index, pos] = value.split(",") as any
    const permissionValue = Math.pow(2, pos);
    if (!(userPermission[index] & permissionValue)) === permissionValue)) {
      el.style.display = 'none'
      console.log("没有权限");
    }
  }
}

后端雷利师傅为了提供的userCode数据,配合我前端绑定给按钮的value值,绝大部分按钮的权限都得以控制。

&在上段代码的作用也得以体现,主要是以特定的位运算对当前绑定v-permission的按钮进行判断,判断当前登录的角色是否具有看得见此按钮进行操作的权限。

二.人在江湖飘,哪能不挨刀?

很快就发现了一个奇怪的问题,有一个按钮不论当前用户有没有权限都无法显示。

通过排查,发现是判断条件内出现问题:userPermission[index] & permissionValue的结果竟然为0。

!(userPermission[index] & permissionValue)) === permissionValue) 
//userPermission[index] == 13572633524226 即2的43次方
//permissionValue == 8796093022208
//开头提到结果 userPermission[index] & permissionValue 应该等于 8796093022208
//即(userPermission[index] & permissionValue)) === permissionValue成立,该用户拥有此按钮的权限
console.log(userPermission[index] & permissionValue) //输出0
//但结果为0

针对上面的情况,我首先想到的是那个没有怎么使用过只存在于传说中的数据类型BigInt,然并卵,当我将数据类型改为BigInt,输出给我的只有0n,哈哈,我直接就傻掉了。

三.天无绝人之路,柳暗花明又一村。

《位运算和权限设计》一文的总结部分中,我发现了作者大大留给我的救命稻草。

Js精度问题:解决13572633524226 & 8796093022208的结果为0

npm install mathjs

通过命令火速安装依赖,然后火速踩坑了(悲。。)

四.math.js

首先介绍一下math.js。

math.js是一个广泛应用于Java 和 Node.js的数学库,它的特点是灵活表达式解析器,支持符号计算,内置大量函数与常量,并提供集成解决方案来处理不同的数据类型,如数字,大数字,复数,分数,单位和矩阵。

看完官方文档的感觉是:好家伙,真是功能强大!

根据官方文档,我发现mathjs自带一个方法bignumber(),但在实操中发现&的结果依然为0,代码如下:

console.log(math.bignumber(userPermission[index]) & math.bignumber(permissionValue)) 
//输出0

于是又开始寻找答案,最终在后端哥雷利的指导下,我才明白原来mathjs也是需要配置的。

const {create, all} = require('mathjs')

// 创建一个带配置的mathjs实例 https://www.mathjs.cn/docs/core/configuration.html
const config = {
  epsilon: 1e-12,// epsilon. 用于测试两个比较值之间是否相等的最小相对差异。所有关系功能都使用该值。默认值为 1e-12.
  matrix: 'Matrix',//matrix. 函数的矩阵输出的默认类型。可用值为:( 'Matrix' 默认值)或'Array'. 。在可能的情况下,函数的矩阵输出类型取决于函数输入:将数组作为输入将返回数组,将矩阵作为输入将返回矩阵。如果没有矩阵作为输入,则输出类型由option决定matrix。对于混合矩阵输入,将始终返回矩阵。
  number: 'BigNumber',// number. 无法通过输入确定数字类型的函数的数字输出类型。但是对于大多数函数而言,输出类型取决于输入:数字作为输入将返回数字作为输出,BigNumber作为输入将返回BigNumber作为输出。
  precision: 64,// precision. BigNumbers的最大有效位数。此设置仅适用于BigNumbers,不适用于数字。默认值为 64.
  predictable: false,//predictable. 功能的可预测输出类型。如果为true,则输出类型仅取决于输入类型。如果为false(默认),则输出类型可以根据输入值而变化。例如,当predictable为falsemath.sqrt(-4) 返回 complex('2i') ,为true NaN时返回。以编程方式处理计算结果时,可能需要可预测的输出,但对于评估动态方程式的用户可能不方便。
  randomSeed: null//randomSeed. 将此选项设置为种子伪随机数生成,使其具有确定性。每次设置此选项时,都会使用提供的种子重置伪随机数生成器。例如,将其设置为每次设置该选项'a' 后将导致在第一次呼叫时 math.random() 返回 0.43449421599986604 。设置为 null 使用随机种子为伪随机数生成器提供种子。默认值为 null.
}

const math = create(all, config)

// 读取应用的配置
console.log(math.config())

在配置完成后,math本身的数字输出类型已变为BigNumber,精度也被提高到64位,于是便可以去实现我们想要的效果。

const userPermission[index] == 13572633524226 
const permissionValue == 8796093022208
console.log(math.evaluate(`${userPermission[index]} & ${permissionValue}`)) 
//这里使用的是mathjs中自带的计算方法evaluate()进行计算
//输出结果为8796093022208,结果正确

就此,把这个坑给填上了。

五.参考文献

《面试官问我按钮级别权限怎么控制,我说v-if,面试官说再见》 作者:街角小林

《JavaScript 中的位运算和权限设计》 作者:云音乐技术团队

《javascript 精度计算/数学运算 mathjs》 作者:HHHHy2019

再次感谢各位大佬的巨作,在让人受益匪浅的同时,提供了业务上的解决问题思路。

六.写在最后

虽然毕业了三天,但我却深知在踩坑之后的痛苦,和解决问题的困难,以上内容旨在为其他开发同学提供思路,因为更多的,我也不会了,欸嘿!

如果各位大佬有更好的解决思路或方法,请多多分享!