V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
3dwelcome
V2EX  ›  JavaScript

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

  •  2
     
  •   3dwelcome · 2021-06-07 16:26:44 +08:00 · 2747 次点击
    这是一个创建于 1025 天前的主题,其中的信息可能已经有所发展或是发生改变。

    好几年前学 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 还真是在不断进化中。

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

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

    js 再加个 int 关键字就好了,现在越来越复杂了。
    lichdkimba
        4
    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
        5
    3dwelcome  
    OP
       2021-06-07 16:52:28 +08:00
    @lichdkimba google 的官方代码应该没错。

    也许从 chrome 某一版本开始,JS 内部语法就支持 var second = first | 0;这种特殊的整型数字定义,只是我们大部分人都不知道罢了。
    geelaw
        6
    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
        7
    3dwelcome  
    OP
       2021-06-07 17:02:28 +08:00
    @geelaw 我肯定信 google 不信你啊。

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

    而且 google 代码也不是编译成 asm.js ,和这个没关系。
    muzuiget
        8
    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
        9
    newmlp  
       2021-06-07 17:14:49 +08:00
    js 里只有浮点数和 bigint 两种数字类型
    geelaw
        10
    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
        11
    3dwelcome  
    OP
       2021-06-07 17:37:39 +08:00


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

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

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

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


    但如果是 Double 且足够大了以后又是原位赋值了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1042 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 22:35 · PVG 06:35 · LAX 15:35 · JFK 18:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.