javascript 的数字,内部表示可以是标准的 32 位整型,而不是浮点数吗?

2021-06-07 16:26:44 +08:00
 3dwelcome

好几年前学 javascript 的时候,网上都说 js 内部数字表达方式就一种,用浮点来表示。

然而我看了 google 的代码后,对这个观点,表示强烈怀疑。

这是 google 的代码库:https://github.com/google/closure-library/blob/master/closure/goog/math/long.js

开头明确写着是 32 位整型:

this.low_ = low | 0; // force into 32 signed bits.

this.high_ = high | 0; // force into 32 signed bits.

我又去翻了一下各种资料,顺藤摸瓜,又找到了类似的文章:

JS 类型声明有固定写法,变量 | 0 表示整数,+变量表示浮点数。

var a = 1;

var x = a | 0;  // x 是 32 位整数
var y = +a;  // y 是 64 位浮点数

上面代码中,变量 x 声明为整数,y 声明为浮点数。支持 asm.js 的引擎一看到 x = a | 0,就知道 x 是整数,然后采用 asm.js 的机制处理。如果引擎不支持 asm.js 也没关系,这段代码照样可以运行,最后得到的还是同样的结果。

再看下面的例子。

// 写法一
var first = 5;
var second = first;

// 写法二
var first = 5;
var second = first | 0;

上面代码中,写法一是普通的 JavaScript,变量 second 只有在运行时才能知道类型,这样就很慢了,写法二是优化 js,second 在声明时就知道是整数,速度就提高了。

也许那么多年,chrome 内核早就对 js 运行期做了一定智能优化,再也不是傻傻的无类型语言了。感叹 JS 还真是在不断进化中。

2916 次点击
所在节点    JavaScript
22 条回复
codehz
2021-06-07 16:39:08 +08:00
确实有优化,v8 里叫做小整数,简称 SMI
(不过是有符号的,所以是 -2**32 到 2**32 - 1 的范围)
放数组里效果最明显,在没有 Uint8Array 等一系列数组的时候,就用纯数字数组也能实现很高的执行效率

可以看这里
https://v8.dev/blog/elements-kinds
codehz
2021-06-07 16:40:06 +08:00
hmmm 打错了,是 2**31(
3dwelcome
2021-06-07 16:45:47 +08:00
@codehz 大佬 V5,那篇文章看下来还挺复杂的。

js 再加个 int 关键字就好了,现在越来越复杂了。
lichdkimba
2021-06-07 16:47:15 +08:00
不是很懂 阮一峰的 es6 教程写的是“JavaScript 所有数字都保存成 64 位浮点数”

https://es6.ruanyifeng.com/#docs/number#BigInt-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B

标准和浏览器的实现可能是两回事吧
3dwelcome
2021-06-07 16:52:28 +08:00
@lichdkimba google 的官方代码应该没错。

也许从 chrome 某一版本开始,JS 内部语法就支持 var second = first | 0;这种特殊的整型数字定义,只是我们大部分人都不知道罢了。
geelaw
2021-06-07 16:56:58 +08:00
这是对注释的误解。参考 MDN:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number

JavaScript 的 Number 等同于 IEEE 754 双精度浮点数。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR

JavaScript 的按位或运算先把运算数转换成 32 位整数再运算,得到的是 Number 。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus

JavaScript 的正号运算把运算数变成 Number 。

你写 var y = 1, z = +1, w = 1.0; 都是没有区别的。另外 JavaScript 的引擎的 “内部” 不需要 “用 IEEE 754 双精度浮点数” “表示” 一个 Number,只要执行效果和 IEEE 754 双精度浮点数一致即可。

楼主说的问题是特定于 asm.js 的人肉优化手段。另外 JavaScript 从来都不是“无类型”的。
3dwelcome
2021-06-07 17:02:28 +08:00
@geelaw 我肯定信 google 不信你啊。

我绝对不相信 google 写出的 js 代码,会把 double 笔误写成 signed 32 bits of the long 。人家谷歌可是开发浏览器的,肯定不会犯这种低级错误。

而且 google 代码也不是编译成 asm.js ,和这个没关系。
muzuiget
2021-06-07 17:07:02 +08:00
> JS 类型声明有固定写法,变量 | 0 表示整数,+变量表示浮点数。

哪里看的垃圾文章,误人子弟。“| 0”表示对 a 和 0 进行位操作“或”,运算时,只是在 VM 里把两个操作数转成 32 整数计算,计算后回到 JS 里依然是个浮点数,只不过值在 32 位整数范围(就是 Google 那句注释的意思)。

> var first = 5;
> var second = first | 0;

别笑死人了,某些 JS 转换器甚至做 tree shaking 会优化成 var second = 5 。
newmlp
2021-06-07 17:14:49 +08:00
js 里只有浮点数和 bigint 两种数字类型
geelaw
2021-06-07 17:18:06 +08:00
@3dwelcome #7 刚看明白,后面那段代码和 Google 没关系。但你对 Google 注释的误读仍然成立,若 a 是 Number,则 JavaScript 表达式 a | 0 对应 C# 表达式

double.IsNaN(a) ? 0.0 : (double)(int)(a)

其中假设 a 在 C# 里具有静态类型 double 。所谓 force into 32 signed bits 是指数值上的截断,不是类型上的改变。
3dwelcome
2021-06-07 17:37:39 +08:00


我用游戏内存修改工具,查了一下 chrome v85 的 var abc = 12345678 | 0; 的内存布局。内部确实是 int32 整型表示的。
3dwelcome
2021-06-07 17:43:07 +08:00
@geelaw "所谓 force into 32 signed bits 是指数值上的截断,不是类型上的改变。"

尾巴上的|0, 就是个内部类型 hint 。代表了内部优先用 int32 位来表示。

JS 有运行期静态分析,如果函数里有复杂计算,Number 内部应该会自动退化到 double 。
cmdOptionKana
2021-06-07 17:50:17 +08:00
标准和实现分开说,就不用争论了。
lujjjh
2021-06-07 18:05:48 +08:00
你确实存在误解,#6 #10 说的是对的。如果我没有猜错,你在 #20 中的例子去掉 | 0 也是同样的效果,这个优化跟 | 0 完全没有关系,| 0 是应该说是某些场景下面给 compiler 的 hint 。

js 引擎的优化多了去了,规范还规定字符串都是 UTF-16,难道 js 引擎内部真的都用 UTF-16 存每个字符串么?只要在外部看起来符合规范,内部用什么黑科技优化都可以。
codehz
2021-06-07 18:34:21 +08:00
@3dwelcome
显然类型上是不会改变的,但是实现只要保证可观测的效果一致就可以了,内部用 smi 优化,对外表现还是和 double 一样,一点问题都没有
类似的优化在 v8 里还有字符串,被细分成单字节编码(仅 ASCII ),双字节编码,乃至还有给 JSON 留的特殊优化路径,这并不影响外部表现是 utf-16 字符串这件事。
只有在需要的时候,才会退化成性能表现更差的内部表示(然后同一个值再也不会回到优化的状态了)
@lujjjh
v8 的 JIT 会根据输入参数的内部表示类型做代码生成,一旦传入非 smi 数字,就有可能导致先前生成的代码失效从而影响性能,内部显式使用|0 之后,即使传入非 smi 数字,但是因为没有产生可观测的差异,所以不会破坏 JIT
EPr2hh6LADQWqRVH
2021-06-07 19:00:41 +08:00
数字内部表示是 int64,但是在进行位运算时会被强转成 int32
EPr2hh6LADQWqRVH
2021-06-07 19:02:02 +08:00
这是 ECMA 里面位操作明确定义的
EPr2hh6LADQWqRVH
2021-06-07 19:03:58 +08:00
@avastms 是 double 不好意思,位运算强转 int32
secondwtq
2021-06-07 19:51:59 +08:00
是啊,再也不是傻傻的无类型语言了。我们 JS 真的是太厉害啦!
no1xsyzy
2021-06-08 09:42:29 +08:00
@3dwelcome 这个显然是优化,不然应当是一重间接指针的(因为是对象,scope -> number object -> stored data )
asm.js 的 `| 0` 的目的是入参强转,使得之后的代码可以很方便地 JIT

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/781924

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX