【问题标题】:Addition with Rust and Webassembly添加 Rust 和 Webassembly
【发布时间】:2021-09-03 20:11:27
【问题描述】:

鉴于我想用 Rust 中的以下函数将系列的前 n 项 1,2,3,.. 相加

fn sum_sequence(x: u64) -> u64 
{
    let mut s: u64 = 0;

    for n in 1..=x
    {
        s = s + n;
    }
    return s;
}

我为 x64 架构编译它时

cargo build --release

并使用x=10000000000 运行它,结果是13106511857580896768 - 很好。

但是当我把这个函数编译成 Webassembly (WASM)

cargo build --target wasm32-unknown-unknown --release

并使用与之前相同的参数运行它,x=10000000000

wasmtime ./target/wasm32-unknown-unknown/release/sum_it.wasm --invoke sum_sequence 1000000000

那么结果是-5340232216128654848

与将 Rust 编译为 WASM 相比,我没有预料到将 Rust 编译为 x64 的结果会有任何偏差。另外,从 WASM 文本文件(如下)中,我不明白为什么在使用 WASM 运行它时会得到否定结果。

WASM 显示不同的结果是怎么来的,我能做些什么来纠正 WASM 的计算?

(module
  (type (;0;) (func (param i64) (result i64)))
  (func $sum_sequence (type 0) (param i64) (result i64)
    (local i64 i64 i32)
    block  ;; label = @1
      local.get 0
      i64.eqz
      i32.eqz
      br_if 0 (;@1;)
      i64.const 0
      return
    end
    i64.const 1
    local.set 1
    i64.const 0
    local.set 2
    block  ;; label = @1
      loop  ;; label = @2
        local.get 1
        local.get 2
        i64.add
        local.set 2
        local.get 1
        local.get 1
        local.get 0
        i64.lt_u
        local.tee 3
        i64.extend_i32_u
        i64.add
        local.tee 1
        local.get 0
        i64.gt_u
        br_if 1 (;@1;)
        local.get 3
        br_if 0 (;@2;)
      end
    end
    local.get 2)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 16)
  (global (;0;) (mut i32) (i32.const 1048576))
  (global (;1;) i32 (i32.const 1048576))
  (global (;2;) i32 (i32.const 1048576))
  (export "memory" (memory 0))
  (export "sum" (func $sum))
  (export "__data_end" (global 1))
  (export "__heap_base" (global 2)))

【问题讨论】:

    标签: rust webassembly


    【解决方案1】:

    这似乎是因为 wasm 不支持原生 u64 作为 type,只支持带符号的变体(特别是 i64),这就是为什么它使用 i64 作为算术运算的类型。由于这会溢出一个 64 位整数(正确的输出是 n * (n+1) / 250000000005000000000,由于溢出,您会得到一个负值,然后将其打印到控制台。这是由于缺少在 wasm 中输入支持。

    仅供参考,Σ n=0 to N := (N * (N+1) / 2,我从这里开始使用它,因为它的计算速度要快得多,并且符合我们的目的。

    结果,50000000005000000000,在内存中需要大约 65.4 位才能在内存中准确表示,这就是为什么你会得到 x86_64 和 wasm 的包装行为,只是它包装的类型不同。

    使用 NumPy,我们可以清楚地确认这一点:

    >>> import numpy as np
    >>> a = np.uint64(10000000000)
    >>> b = np.uint64(10000000001)
    >>> (a >> np.uint64(1)) * b
    13106511857580896768
    
    >>> import numpy as np
    >>> a = np.int64(10000000000)
    >>> b = np.int64(10000000001)
    >>> (a >> np.int64(1)) * b
    -5340232216128654848
    

    您得到的值是由于无符号和有符号(二进制补码)整数溢出。 (注意:我正在使用正确的位移来模拟除以二,我可能还可以使用// 运算符)。

    编辑:此外,Herohtar 在 cmets 中提出了一个很好的观点:如果在调试模式下运行,它显然会溢出,并与 'attempt to add with overflow' 发生恐慌。

    【讨论】:

    • 如果您在执行检查算术的调试中运行,您还可以看到 Rust x64 版本溢出,因为它最终会出现 'attempt to add with overflow' 恐慌
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-26
    • 2022-01-17
    • 2020-11-22
    • 2021-09-22
    • 1970-01-01
    相关资源
    最近更新 更多