我正在尝试将简单的 JS 代码转换为 C 和/或 Perl,但我发现对整数进行算术运算 (
+ - * / << >>
) 时行为存在差异,并且结果溢出。我需要精确地模拟JS,甚至溢出! JS 变量不是显式的 BigInt,而只是 JS var。
JS(通过node.js或Firefox的开发者工具控制台):
function calc(a, b){
return (a<<b) + (a<<b);
}
var x = calc(1, 30);
result:
2147483648
C:
#include <stdio.h>
#include <stdint.h>
int main(void){
int32_t lop = 1<<30; //1073741824;
int32_t rop = 1<<30; //1073741824;
int32_t res = lop + rop;
printf("1<<30 + 1<<30 : %d\n", res);
}
result:
1<<30 + 1<<30 : -2147483648
Perl:
sub to32bit { unpack("l", pack("L", $_[0])) } # Corion @ PerlMonks
print to32bit(to32bit(1<<30)+to32bit(1<<30));
result:
-2147483648
我的说法是否存在差异,或者我做错了什么?
如何做到这一点?
正如我所说,我需要在 C/Perl 中模拟 JS 的确切行为,所以我不想修复 JS 代码,而是调整 C/Perl 代码以执行与 JS 相同的操作。
另请参阅相关讨论:https://perlmonks.org/?node_id=11155911
2024 年 2 月编辑:现在有 Sisyphus 的 CPAN 模块 Math::JS(位于 https://metacpan.org/pod/Math::JS)。
JavaScript 是 ECMAScript 的实现,ECMAScript 指定执行左移操作,就像在执行操作之前将左操作数转换为 32 位二进制补码整数,将右操作数转换为 32 位无符号整数整数。它还指定使用 IEEE-754 二进制 64(“双精度”)形式的数字执行加法。
假设您的 C 实现对
double
使用 binary64(这很常见),并且 int32_t
和 uint32_t
可用(在 <stdint.h>
标头中),则 JavaScript (a<<b) + (a<<b)
很大程度上相当于:
(double) ((int32_t) a << (uint32_t) b) + (double) ((int32_t) a << (uint32_t) b)
我说“很大程度上等效”是因为 ECMAScript 和 C 之间涉及类型之间转换或处理无穷大、NaN 和其他异常情况的操作细节可能有所不同。
ECMAScript 规范对于操作的语义是明确的。如果您要将 JavaScript 转换为另一种语言,您应该获取一份 ECMAScript 语言规范 并使用它。
Javascript“数字”本质上是
double
,在进行位算术时,它们被截断为int32
,然后提升回双精度。所以你的 C 代码应该是这样的
double js_shl(double a, double b) {
return (double) ((int32_t)a << (int32_t)b);
}
double calc(double a, double b) {
return js_shl(a, b) + js_shl(a, b);
}
int main(void){
double res = calc(1, 30);
printf("result : %f\n", res);
}