从 Raku nativecall 调用时精度不同

Sum*_*nal 5 c rakudo nativecall raku

我正在寻找编写linspaceNumPy 的函数。

由于编译代码中的循环速度更快,因此尝试用 C 编写并从 Raku 调用。

//  C code
#include <stdio.h> 
#ifdef _WIN32 
#define DLLEXPORT __declspec(dllexport)
#else 
#define DLLEXPORT extern // if c++ code, requires extern "C"
#endif

DLLEXPORT void c_linspace(double start, double step, int num, double* vals) {
    for (int i = 0; i < num; i++)
{
    vals[i] = start;
    start += step;
}
}
Run Code Online (Sandbox Code Playgroud)
// Raku code
sub c_linspace(num64, num64, int32, CArray[num64]) 
    is native( MYDYN) { * };

sub raku_linspace($start, $end, $num, :$endpoint = True, :$retstep = False) {
    my $step = $endpoint ?? ($end - $start)/($num - 1) !!  ($end-$start)/($num);
    my $vals = CArray[num64].allocate($num);
    c_linspace($start.Num, $step.Num, $num.Int, $vals);
    $retstep ?? ($vals.list, $step) !! $vals.list
}
Run Code Online (Sandbox Code Playgroud)

工作正常,但检查结果的精度,例如:

拉库给出:

say raku_linspace(1,3,300000)[200]   # gives 1.0013333377777869
Run Code Online (Sandbox Code Playgroud)

虽然 NumPy 给出:

// Raku code
sub c_linspace(num64, num64, int32, CArray[num64]) 
    is native( MYDYN) { * };

sub raku_linspace($start, $end, $num, :$endpoint = True, :$retstep = False) {
    my $step = $endpoint ?? ($end - $start)/($num - 1) !!  ($end-$start)/($num);
    my $vals = CArray[num64].allocate($num);
    c_linspace($start.Num, $step.Num, $num.Int, $vals);
    $retstep ?? ($vals.list, $step) !! $vals.list
}
Run Code Online (Sandbox Code Playgroud)

为什么精度会出现这种差异?我的期望是double保持精确到 15 位小数。

文档提到的内容double映射到num64.

还提到了Rakudo 特定类型long,其中longlong提到了 , 。那么 Raku 中映射到什么long double。看来不是num64

系统信息
Windows 10 64位
gcc 13.2.0

由于浮点错误,问题似乎直接出现在 C 中,而不是通过 nativecall 调用。因为运行 C 可以提供 Raku 所提供的东西。

然而,nativecall中有没有机制可以处理long double或者long long最好有一个例子?

另外,如何才能使该函数具有与 NumPy 相同的精度?

Mas*_*ssa 3

我不知道我们是否应该取消标记这个问题,编辑它,或者其他事情,但无论如何:这不是一个 NativeCall 问题,也不是一个 Raku 问题,它是一个 C 问题,这里的“真正”问题是:

为什么我c_linspace生成的结果不同numpy.linspace

这个问题的答案是:numpy使用linspace了不同的计算方式,可能是使用SMP指令。它确实(稍微简化):

    y = _nx.arange(0, num, dtype=dt).reshape((-1,) + (1,) * ndim(delta))
    step = delta / div
    y *= step
    y += start
Run Code Online (Sandbox Code Playgroud)

C 等价物是:

void c_linspace(double start, double step, int num, double* vals) {
  for (int i = 0; i < num; i++)
    vals[i] = (double) i;
  for (int i = 0; i < num; i++)
    vals[i] *= step;
  for (int i = 0; i < num; i++)
    vals[i] += start;
}
Run Code Online (Sandbox Code Playgroud)

1.0013333377777927如果您使用此实现,您的 Raku 代码将返回与版本相同的结果numpy

但是,正如评论中所说,即使

void c_linspace(double start, double step, int num, double* vals) {
  for (int i = 0; i < num; i++)
    vals[i] = i * step + start;
}
Run Code Online (Sandbox Code Playgroud)

就足够了...

回答你的另一个问题,即

然而,nativecall中有没有机制可以处理long double或者long long最好有一个例子?

long long在 rakudo 中受支持,该类型被称为longlong或其ulonglong无符号变体。该类型尚不支持(还?),但在https://github.com/rakudo/rakudo/blob/main/lib/NativeCall.rakumodlong double中添加对它的支持应该很容易(尽管不是微不足道的)(这不是微不足道的)部分是完成测试等)。

  • 这也是 SIMDable 的。NumPy 之所以能够成功,是因为它关心边缘情况 (2认同)