基于现在市面上到处都是 Vue/React 之类的源码分析文章实在是太多了。(虽然我也写过 Vite的源码解析

所以这次来写点不一样的。由于微信这边用的是 protobuf 来进行 rpc 调用。所以有时候需要将 JS 中的 Number 类型转换为 Long 类型传给后端。目前用的最多的就是 Long.js 了,然而市面上分析这个库的文章少的出奇。唯一能搜到的一篇讲的也非常的简略 Long.js源码分析与学习

其实想要弄懂它的源码还是非常难的。需要十分了解原码 反码 补码 的相关知识知道计算机进行四则运算的原理以及了解 IEEE754 浮点数标准。不过懂了之后对位运算的理解以及 JS中数字的存储规则以及为什么 0.1+0.2 = 0.30000000000000004 这种问题都有了完美的答案了。我 fork 下来的仓库是https://github.com/zhangyuang/long.js-design/blob/master/src/long.js
其中包含了我这篇文章所写的中文注释

下一步是写 Rust 版本的 long.js 来看看能否提升性能,虽然 long.js 本身已经用了 wasm,看起来提升空间不大

简介

A Long class for representing a 64 bit two's-complement integer value derived from theClosure Libraryfor stand-alone use and extended with unsigned support.

顾名思义。long.js 使用了 高位(high) 低位 (low) 分别是32位有符号数补码的形式来表示一个64位数。这是因为在 js 的世界中 最大的安全数的范围是 `-2^53 -1 ~ 2^53-1` 并且在进行位运算的时候 js 会先将数字转为 32位有符号数补码在进行运算。

IEEE754

Please read this article for understand IEEE754

32位浮点数存储规则

64为浮点数存储规则

为什么最大的安全数字是 2^53 - 1

As IEEE754 says, double type variable has 1 sign, 11 exponent, 52 fraction。
Due to Number 1 is the value beginning default, so the exponent max value is 53. why MAX_SAFE_INTEGER is 2^53 - 1 because if number is more than 2^53 such as 2^53 and 2^53 + 1 whose fraction will be same.

根据 IEEE754 标准。64为双精度浮点数的存储规则由1位符号位,11位指数位,52位数值位组成。同时IEEE754规定尾数第一位隐含为1不写,所以一共有53位。为什么超过了 2^53 -1 就是不安全的。因为超出了的数字在二进制的表示基础上,总有另一个数字的二进制表示跟它一样。所以无法区分它们之间的关系的正确性。

reference

【算法】解析IEEE 754 标准​www.cnblogs.com

源码解析

fromInt

function fromInt(value, unsigned) {// 32 位数转 long类型// 对超出32位数的情况以及负数的情况都做了特殊处理// Long { low: 16777216, high: 0, unsigned: false }var obj, cachedObj, cache;if (unsigned) {// 无符号数value >>>= 0; // 无符号右移0保证得到的是无符号数,if (cache = (0 <= value && value < 256)) {// 只缓存0-256之间的结果cachedObj = UINT_CACHE[value];if (cachedObj)return cachedObj;}// 这里|0是因为位运算会转成32位有符号数补码在进行计算。// 保证得到的数字的正负值的正确性如果超出 Math.pow(2,31) -1 则为负数obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); if (cache)UINT_CACHE[value] = obj;return obj;} else {// 有符号数// 如果value大于2147483647则得到的结果为负数如 2147483648 Long { low: -2147483648, high: -1, unsigned: false }value |= 0;if (cache = (-128 <= value && value < 128)) {// 只缓存-128到128之间的结果cachedObj = INT_CACHE[value];if (cachedObj)return cachedObj;}obj = fromBits(value, value < 0 ? -1 : 0, false);if (cache)INT_CACHE[value] = obj;return obj;}
}

fromNumber

function fromNumber(value, unsigned) {if (isNaN(value))// 不合法数字返回有符号0/无符号0return unsigned ? UZERO : ZERO;if (unsigned) {if (value < 0)// 如果是无符号且传入负数则返回0return UZERO;if (value >= TWO_PWR_64_DBL)// 如果数字大于等于 Math.pow(2,64) 即 64 位无符号数能表示的最大值 Math.pow(2,64)-1// 返回最大的无符号数 即 fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); Long { low: -1, high: -1, unsigned: true }return MAX_UNSIGNED_VALUE;} else {// 有符号数最大值是Math.pow(2,63)-1最小值是-Math.pow(2,63)if (value <= -TWO_PWR_63_DBL)return MIN_VALUE; // fromBits(0, 0x80000000|0, false); Long {low: 0, high: -2147483648, unsigned: false}if (value >= TWO_PWR_63_DBL - 1) // 这里改成 -1 更容易理解return MAX_VALUE; // fromBits(0xFFFFFFFF, 0x7FFFFFFF|0, false); Long {low: -1, high: 2147483647, unsigned: false}}if (value < 0)return fromNumber(-value, unsigned).neg(); // 如果是负数则先转换为正数再取反+1return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); // 分成2个32位有符号数来表示
}

fromString

function fromString(str, unsigned, radix) {if (str.length === 0)throw Error('empty string');if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") // 非法字符串返回0return ZERO;if (typeof unsigned === 'number') {// 输入的无符号标志为number则赋值给radix// For goog.math.long compatibilityradix = unsigned,unsigned = false;} else {unsigned = !! unsigned;}radix = radix || 10; // 默认是10进制if (radix < 2 || 36 < radix)throw RangeError('radix');var p;if ((p = str.indexOf('-')) > 0) // 如果字符串包含 - 则抛错throw Error('interior hyphen');else if (p === 0) {// 如果是负数,则返回对应正数的取反+1值return fromString(str.substring(1), unsigned, radix).neg();}// Do several (8) digits each time through the loop, so as to// minimize the calls to the very expensive emulated div.var radixToPower = fromNumber(pow_dbl(radix, 8)); // Math.pow(radix, 8)var result = ZERO;for (var i = 0; i < str.length; i += 8) {var size = Math.min(8, str.length - i), // 取剩下的长度和8的更小值value = parseInt(str.substring(i, i + size), radix); // 每次处理8位字符串转换为number类型如果不足8位则取剩下的所有字符if (size < 8) {var power = fromNumber(pow_dbl(radix, size)); //如果剩下的数字不足8位,则先乘以剩下的数字的位数result = result.mul(power).add(fromNumber(value)); // 再把结果加上数字本身} else {// 如果长度大于8则处理// 之前处理的结果先乘以Long { low: 100000000, high: 0, unsigned: false }result = result.mul(radixToPower);result = result.add(fromNumber(value)); // 加上这次的数字}}result.unsigned = unsigned;return result;
}

add

LongPrototype.add = function add(addend) {// Long 对象相加// 把每个64位对象分成4块。每16位为一块// 如果不分块32位为一块,当结果溢出时会丢失进位if (!isLong(addend))addend = fromValue(addend);// Divide each number into 4 chunks of 16 bits, and then sum the chunks.var a48 = this.high >>> 16; // 得到高位的前16位var a32 = this.high & 0xFFFF; // 得到高位的后16位var a16 = this.low >>> 16; // 得到低位的前16位var a00 = this.low & 0xFFFF; // 得到低位的后16位var b48 = addend.high >>> 16;var b32 = addend.high & 0xFFFF;var b16 = addend.low >>> 16;var b00 = addend.low & 0xFFFF;var c48 = 0, c32 = 0, c16 = 0, c00 = 0;c00 += a00 + b00;c16 += c00 >>> 16; // 低位后16位相加,然后右移16位。只保留超过16位的进位c00 &= 0xFFFF; // 只保留后16位c16 += a16 + b16; // 同样重复上述操作。每次对16位进行运算。保留进位,同时本身只保留16位c32 += c16 >>> 16;c16 &= 0xFFFF;c32 += a32 + b32;c48 += c32 >>> 16;c32 &= 0xFFFF;c48 += a48 + b48;c48 &= 0xFFFF;// 低位的前16位左移然后与上低位的后16位,得到一个正确的32位有符号数return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
};

multiply

LongPrototype.multiply = function multiply(multiplier) {// Long 类型相乘if (this.isZero())return ZERO;if (!isLong(multiplier))multiplier = fromValue(multiplier);// use wasm support if presentif (wasm) {// 使用wasm提升性能var low = wasm["mul"](this.low,this.high,multiplier.low,multiplier.high);return fromBits(low, wasm["get_high"](), this.unsigned);}if (multiplier.isZero())// 被乘数是0则返回0return ZERO;if (this.eq(MIN_VALUE))// 如果被乘数是奇数则返回最小值,否则返回0return multiplier.isOdd() ? MIN_VALUE : ZERO;if (multiplier.eq(MIN_VALUE))return this.isOdd() ? MIN_VALUE : ZERO;if (this.isNegative()) {if (multiplier.isNegative())return this.neg().mul(multiplier.neg());elsereturn this.neg().mul(multiplier).neg();} else if (multiplier.isNegative())return this.mul(multiplier.neg()).neg();// If both longs are small, use float multiplicationif (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24))return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned);// Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.// We can skip products that would overflow.// 同样把 Long 对象分为4块var a48 = this.high >>> 16; // 高位的前16位var a32 = this.high & 0xFFFF; // 高位的后16位var a16 = this.low >>> 16;var a00 = this.low & 0xFFFF;var b48 = multiplier.high >>> 16;var b32 = multiplier.high & 0xFFFF;var b16 = multiplier.low >>> 16;var b00 = multiplier.low & 0xFFFF;var c48 = 0, c32 = 0, c16 = 0, c00 = 0;c00 += a00 * b00;c16 += c00 >>> 16; // c16默认为加上低位后16位相乘的进位c00 &= 0xFFFF; // 只保留后16位c16 += a16 * b00; // 被乘数的后16位与乘数的前16位相乘并且加上之前的进位c32 += c16 >>> 16; // 加上结果的进位c16 &= 0xFFFF;c16 += a00 * b16;c32 += c16 >>> 16;c16 &= 0xFFFF;c32 += a32 * b00;c48 += c32 >>> 16;c32 &= 0xFFFF;c32 += a16 * b16;c48 += c32 >>> 16;c32 &= 0xFFFF;c32 += a00 * b32;c48 += c32 >>> 16;c32 &= 0xFFFF;// 高位的前16位,再加上前面的进位之后再加上本值。溢出的进位则舍去不考虑。// 因为最终的结果还是 4 chunks组成的对象。所以不需要考虑 a48 * b16 这样的结果,因为超出了64位的范畴会舍去c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;c48 &= 0xFFFF;return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
};

neg

LongPrototype.negate = function negate() {if (!this.unsigned && this.eq(MIN_VALUE))return MIN_VALUE;return this.not().add(ONE); // 取反+1,原码->补码
};

js怎么调用wasm_Long.js源码解析相关推荐

  1. JavaScript | 益智类数字棋牌小游戏,无游戏框架,浏览器直接运行JavaScript(js)小游戏【源码+解析】

    游戏界面 曾几何时想有一款自己编写的游戏,通过不断学习,终于掌握了一点JavaScript基础,捣鼓了一个益智类的数字棋牌游戏,没有使用任何游戏框架,就简单使用HTML做布局,CSS做动画,JavaS ...

  2. 海思处理器sensor驱动调用过程与源码解析与实战

    准备知识: 在mpp/sample/makefile.param /*gcc 的 -D选项作用要注意*/ CFLAGS += -Wall -g $(INC_FLAGS) -D$(HIARCH) -DH ...

  3. Dubbo 实现原理与源码解析系列 —— 精品合集

    摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.[芋艿]精尽 Dubbo 原理与源码专栏 2.[ ...

  4. 微服务开源框架TARS的RPC源码解析 之 初识TARS C++服务端

    作者:Cony 导语:微服务开源框架TARS的RPC调用包含客户端与服务端,<微服务开源框架TARS的RPC源码解析>系列文章将从初识客户端.客户端的同步及异步调用.初识服务端.服务端的工 ...

  5. 【Vue.js源码解析 一】-- 响应式原理

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...

  6. JavaScript数字运算必备库——big.js源码解析

    概述 在我们常见的JavaScript数字运算中,小数和大数都是会让我们比较头疼的两个数据类型. 在大数运算中,由于number类型的数字长度限制,我们经常会遇到超出范围的情况.比如在我们传递Long ...

  7. 迷你 JS 框架 Hyperapp 源码解析

    Hyperapp 是最近热度颇高的一款迷你 JS 框架,其源码不到 400 行,压缩 gzip 后只有 1kB,却具有相当高的完成度,拿来实现简单的 web 应用也不在话下.整体实现上,Hyperap ...

  8. 购物车(js+css+html)源码解析

    购物车源码解析 先取得表格: Js代码 1.var table = document.getElementById("table"); 然后遍历表格的行数进行删除: Js代码 1. ...

  9. Retrofit2源码解析——网络调用流程(下)

    Retrofit2源码解析系列 Retrofit2源码解析(一) Retrofit2源码解析--网络调用流程(上) 本文基于Retrofit2的2.4.0版本 implementation 'com. ...

最新文章

  1. linux1.0内核下载,《Linux 0.01 内核分析与操作系统设计》(Linxu 0.01Source)
  2. 【面试锦囊】位运算介绍与经典例题总结
  3. java velocity是什么意思,什么是Apache Velocity?
  4. checkbox复选框样式
  5. Qt定时器QBasicTimer、startTimer、QTimer使用总结
  6. 设计灵感|怎么设计渐变海报更有趣味性?
  7. 工程师们,不要想一辈子靠技术混饭吃
  8. oracle高级函数api,Oracle函数-高阶篇
  9. linux桌面隐藏下面任务栏,CentOS 7 隐藏任务栏和顶栏,centos任务栏
  10. 什么是DMZ区域,DMZ区域的作用与原理
  11. rabbitmq 存入mysql_将RabbitMQ使用者数据保存到数据库中
  12. 软件资源学生优惠合集
  13. Liunx-安装SonarQuble
  14. 数据仓库建设之数仓架构
  15. html页面上传文件mui,mui 文件上传注意问题
  16. 一个项目的经验教训:关于打乱和拆分数据
  17. SLF4J日志框架在项目中使用
  18. ​谁是信创担当——《2021中国信创生态市场研究报告》正式发布
  19. DR007利率报价查询_图表加数据DR007存款类机构质押式回购利率
  20. java时间格式转换pm,将字符串转换为日期和时间为am / pm格式

热门文章

  1. lnmp化境开启pathinfo,支持tp5.0等访问
  2. Python 标准类库-Windows特殊服务之msvcrt
  3. Oracle存储过程--案例
  4. 关于在unity中动态获取字符串后在InputField上进行判断的BUG
  5. 第五周 Leetcode 99. Recover Binary Search Tree (HARD)
  6. 3D打印材料PLA,ABS对比
  7. Vue项目代码改进(二)—— element-UI的消息显示时间修改
  8. 【C++STL/红黑树】POJ 3481 DoubleQueue
  9. 使用 Drone 构建 Coding 项目
  10. Linux下进行Web服务器压力(并发)测试工具http_load、webbench、ab、Siege、autobench简单使用教程(转)...