js怎么调用wasm_Long.js源码解析

61a7fb91fac300d456302ceecd6dec1c.png

基于现在市面上到处都是 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

a634bb37df98636a97ad6004493967cc.png

32位浮点数存储规则

a21d3001cf16066327f9f1ab8baa5ff3.png

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
6b094a5597257c18af289a4efddbf578.png

源码解析

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,原码->补码
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262520.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AOP快速入门

一、概念 AOP面向切面编程&#xff0c;是函数式编程的延申&#xff0c;是对OOP的补充&#xff1b; 代理模式&#xff1a;拦截增强作用&#xff0c;增强功能&#xff1b; 1、java继承&#xff0c;纵向共性抽取&#xff0c; 2、横向切面AOP织入增强代码方式 二、原理是通过代理机…

图形大小_PS图形复制——等距复制、旋转复制、大小变换复制

PS中图形复制是很常见的事&#xff0c;可能不同人的习惯可能会用不同的方法。这里糖糖主要讲的是图形的一些等距复制、旋转复制、大小变换旋转复制&#xff0c;相信看过本篇文章之后你也可以通过图形的不同需要的复制做一些很不错的图案哦~糖糖先在PS里用钢笔工具绘制了一个绿叶…

AOP联盟通知类型和Spring编写代理半自动

一、cglib功能更强大 二、Spring核心jar包 三、AOP联盟通知 三、代码实现Spring半自动代理 1、环绕通知的切面 2、bean.xml配置 3、创建bean容器&#xff0c;获取bean&#xff0c;即已经创建好的代理对象&#xff1a; 4、配置多个接口 转载于:https://www.cnblogs.com/wmqiang/…

1到30图片大全顺序_终于解决了!自媒体作者,如何给 Word 中的图片批量编号?...

今天这个技巧非常有用&#xff0c;特别是对小编这种每天都要写图文教程的人来说&#xff0c;简直是续命神技。如果 Word 中有大量的图片&#xff0c;经常需要个每个图片顺序编号&#xff0c;有没有同款需求&#xff1f;大家平时都一个个手工输入的吗&#xff1f;编到后来很可能…

Android Volley框架的使用(二)

此博文源码下载地址 https://github.com/Javen205/VolleyDemo.git 使用请求队列RequestQueue Volley中的Request都需要添加到RequestQueue中才能执行&#xff0c;所以首先需要创建一个RequestQueue RequestQueue Volley.newRequestQueue(mContext); 通常情况在一个应用中需要…

Spring全自动AOP和项目加入jar包

一、jar可以引进项目中&#xff0c;复制到路下后&#xff0c;要add as library&#xff0c;加载到工作空间中才能引入&#xff1b; 也jar包放在硬盘的项目目录外面&#xff0c;可以多个项目引入共用&#xff1a; 二、xml配置 1、aop全自动配置 2、xml装配bean,不是注解方式 3、…

中怎样载入选区_ps中快捷大全

使用ps时使用快捷键更方便&#xff0c;操作起来更能提高工作效率&#xff0c;下面给大家总结一下&#xff0c;希望对大家有帮助&#xff0c;当然大家有更好的技巧可以来魔课66网一起交流一下文件设置:Ctrln——新建Ctrlo——打开Ctrlp——打印Ctrls—— 存储Ctrlw——关闭Ctrl …

AspectJ——AOP框架快速入门

一、导包 二、bean.xml配置 三、环绕通知 四&#xff0c;表达式 转载于:https://www.cnblogs.com/wmqiang/p/11617042.html

linux桌面环境应用

为什么80%的码农都做不了架构师&#xff1f;>>> 通常的 Linux 发行版都使用 KDE 或者 GNOME 作为默认的桌面环境。它们都给用户提供了一个原始的并且有吸引力的桌面&#xff0c;并且内置了各式各样的多媒体软件、系统程序、游戏、实用程序、网页开发工具、编程 工具…

[转]C++的坑真的多吗?

http://coolshell.cn/articles/7992.html#jtss-tsina 先说明一下&#xff0c;我不希望本文变成语言争论贴。希望下面的文章能让我们客观理性地了解C这个语言。&#xff08;另&#xff0c;我觉得技术争论不要停留在非黑即白的二元价值观上&#xff0c;这样争论无非就是比谁的嗓门…

MMC无法创建管理单元

解决方法&#xff1a;经过我多方考证&#xff0c;原来只是一个小小的问题。那就是”环境变量”在作怪 1. 右键单击“我的电脑”&#xff0c;然后单击“属性”。 2. 在“高级”选项卡上&#xff0c;单击“环境变量”。 3. 在“系统变量”下&#xff0c;双击“PATH 环境变量”。…

第一个Sprint冲刺第六天

讨论成员&#xff1a;邵家文、李新、朱浩龙、陈俊金 讨论问题&#xff1a;解决编写代码的问题 讨论地点&#xff1a;宿舍 进展&#xff1a;已开始对代码的编写 转载于:https://www.cnblogs.com/shaojiawen/p/4970305.html

AspectJ基于xml和基于注解

一、基于xml 执行的切入点中具体方法有返回值&#xff0c;则方法结束会立即执行后置通知&#xff0c;然后再执行环绕通知的放行之后的代码&#xff1b; 2、连接点即所有可能的方法&#xff0c;切入点是正真被切的方法&#xff0c;连接点方法名&#xff1a; 其中&#xff0c;只有…

设置堆内存大小_jmap和jhat命令行工具的配合使用,更好的掌握堆内存状况

Java的内存映像工具&#xff0c;jmap&#xff0c;Memory Map for Java&#xff0c;用于生成堆转储快照&#xff0c;一般成为heapdump或者dump文件&#xff0c;出了获取dump文件&#xff0c;这个工具还可以查询finalize执行队列&#xff0c;Java堆和永久代的详细信息&#xff0c…

sessionState 配置方案

配置SQL Server Session方法 以下过程是在Win 2003 SP2 IIS 6.0, ASP.NET 2.0, SQL Server 2005下进行的。 1. 安装Session数据库 到Framework目录 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727运行下面的命令: aspnet_regsql.exe -ssadd -sstype c -d [DB] -S [Server] …

Open*** 服务器的搭建

服务介绍直译就是虚拟专用通道&#xff0c;是提供给企业之间或者个人与公司之间安全数据传输的隧道&#xff0c;Open无疑是Linux下开源的先锋&#xff0c;提供了良好的性能和友好的用户GUI。它大量使用了OpenSSL加密库中的SSLv3/TLSv1协议函数库。实验拓扑图实验环境xuegod 63 …

怎么批量选择目标_全日制专升本一次可以报几个学校?目标院校应该怎么选择?...

很多普通专升本考生们在备考的时候会对报考环节存在疑问&#xff0c;比如&#xff0c;普通专升本可以报几个学校&#xff1f;报考院校怎么选择&#xff1f;下面我们就这两个问题进行解答。想要知道湖北普通专升本考试一次可以报几个院校和专业&#xff0c;我们不妨先来看看《省…

jdbcTemplate快速入门

一、 c3p0和dbcp区别 二、导包 hibernate通过映射自动创建表&#xff1b; 三、代码实现 转载于:https://www.cnblogs.com/wmqiang/p/11617387.html

bean.xml配置数据源和读取配置文件配置数据源

一、bean.xml配置数据源 bean.xml装配bean&#xff0c;依赖注入其属性的时候&#xff0c;对应实体类中属性一定要有set方法&#xff0c; 二、读取配置文件配置数据源 1、配置文件 bean.xml配置&#xff1a; classpath就是src目录下&#xff0c;${ } 当作是Spring表达式&#xf…

事务保存点savepoint

一、 转载于:https://www.cnblogs.com/wmqiang/p/11618714.html