1

我正在尝试使用CAMPARY库(CudA 多精度算术库)。我已经下载了代码并将其包含在我的项目中。由于它同时支持 cpu 和 gpu,我从 cpu 开始了解它是如何工作的,并确保它能够满足我的需求。但目的是将其与 CUDA 一起使用。

我能够实例化一个实例并分配一个值,但我不知道如何让事情恢复原状。考虑:

#include <time.h>
#include "c:\\vss\\CAMPARY\\Doubles\\src_cpu\\multi_prec.h"

int main()
{
    const char *value = "123456789012345678901234567";

    multi_prec<2> a(value);

    a.prettyPrint();
    a.prettyPrintBin();
    a.prettyPrintBin_UnevalSum();
    char *cc = a.prettyPrintBF();
    printf("\n%s\n", cc);
    free(cc);
}

编译、链接、运行(VS 2017)。但是输出非常无用:

Prec = 2
   Data[0] = 1.234568e+26
   Data[1] = 7.486371e+08

Prec = 2
   Data[0] = 0x1.987bf7c563caap+86;
   Data[1] = 0x1.64fa5c3800000p+29;

0x1.987bf7c563caap+86 + 0x1.64fa5c3800000p+29;

1.234568e+26 7.486371e+08

像这样打印每个双精度数可能很容易,但它并不能告诉您存储的 128 数字的值。如果无法输出结果,那么执行高精度计算的价值是有限的。

除了打印出值之外,最终我还需要将这些数字转换为整数(如果有打印方法,我愿意在浮点数中全部尝试,但我担心准确性和速度都会受到影响)。与MPIR(不支持 CUDA)不同,CAMPARY 没有任何关联的多精度 int 类型,只有浮点数。我可能可以拼凑出我需要的东西(主要是加/减/比较),但前提是我可以取出 CAMPARY 值的整数部分,我看不出有什么办法。

CAMPARY 似乎没有任何文档,因此可以想象这些功能在那里,而我只是忽略了它们。我宁愿在 CAMPARY 讨论论坛/邮件列表上询问,但似乎没有。这就是我在这里问的原因。

总结一下:

  1. 有什么方法可以multi_prec<2>从 CAMPARY 输出 128bit ( ) 值吗?
  2. 有没有办法从 CAMPARY multi_prec 中提取整数部分?也许我不理解的库中的(许多)数学函数之一计算了这个?
4

1 回答 1

1

这个问题实际上只有两种可能的答案:

  1. 还有另一个(更好的)多精度库可以在 CUDA 上运行,可以满足您的需求。
  2. 以下是如何修改此库以执行您需要的操作。

唯一能给出第一个答案的人是 CUDA 程序员。不幸的是,如果有这样的图书馆,我相信 talonmies 会知道并提到它。

至于 #2,如果他们不是 CUDA 程序员,为什么有人会更新这个库?还有其他更好的多精度库。CAMPARY 提供的唯一好处是它支持 CUDA。这意味着唯一有真正动机使用或修改库的人是 CUDA 程序员。

而且,作为对解决这个问题最感兴趣的 CUDA 程序员,我确实想出了一个解决方案(尽管是一个丑陋的解决方案)。我在这里发布它是希望这些信息对未来的 CAMPARY 程序员有价值。这个库没有太多信息,所以这是一个开始。


您需要了解的第一件事是 CAMPARY 如何存储其数据。而且,虽然并不复杂,但它不是我所期望的。来自 MPIR,我假设 CAMPARY 以几乎相同的方式存储其数据:固定大小的指数后跟任意数量的尾数位。

但是不,CAMPARY 走了一条不同的路。查看代码,我们看到:

private:
    double data[prec];

现在,我假设这只是保留所需位数的任意方式。但是不,他们确实使用prec双打。像这样:

multi_prec<8> a("2633716138033644471646729489243748530829179225072491799768019505671233074369063908765111461703117249");

    // Looking at a in the VS debugger:

    [0] 2.6337161380336443e+99  const double
    [1] 1.8496577979210756e+83  const double
    [2] 1.2618399223120249e+67  const double
    [3] -3.5978270144026257e+48 const double
    [4] -1.1764513205926450e+32 const double
    [5] -2479038053160511.0 const double
    [6] 0.00000000000000000 const double
    [7] 0.00000000000000000 const double

因此,他们所做的是在第一个 double 中存储可能的最大精度,然后将余数用于计算下一个 double,依此类推,直到它们包含整个值,或者精度不足(删除最低有效位)。请注意,其中一些是负数,这意味着前面的值的总和比实际值大一点,他们正在向下修正。

考虑到这一点,我们回到如何打印它的问题。

理论上,您可以将所有这些加在一起以获得正确的答案。但是根据定义,我们已经知道 C 没有数据类型来保存这个大小的值。但是其他库可以(比如 MPIR)。现在,MPIR 在 CUDA 上不起作用,但它不需要。您不想让您的 CUDA 代码打印出数据。无论如何,这是您应该从主机做的事情。因此,使用 CUDA 的全部功能进行计算,cudaMemcpy 将结果返回,然后使用 MPIR 将它们打印出来:

#define MPREC 8
void ShowP(const multi_prec<MPREC> value)
{
    multi_prec<MPREC> temp(value), temp2;

    // from mpir at mpir.org
    mpf_t mp, mp2;

    mpf_init2(mp, value.getPrec() * 64); // Make sure we reserve enough room
    mpf_init(mp2); // Only needs to hold one double.

    const double *ptr = value.getData();

    mpf_set_d(mp, ptr[0]);

    for (int x = 1; x < value.getPrec(); x++)
    {
        // MPIR doesn't have a mpf_add_d, so we need to load the value into
        // an mpf_t.
        mpf_set_d(mp2, ptr[x]);
        mpf_add(mp, mp, mp2);
    }

    // Using base 10, write the full precision (0) of mp, to stdout.
    mpf_out_str(stdout, 10, 0, mp); 

    mpf_clears(mp, mp2, NULL);
}

与存储在上面的 multi_prec 中的数字一起使用,这将输出完全相同的值。耶。

这不是一个特别优雅的解决方案。必须添加第二个库只是为了从第一个库中打印一个值显然是次优的。而且这种转换也不会那么快。但是打印的频率通常比计算低得多。如果您进行一个小时的计算和少量打印,那么性能并不重要。而且它根本无法打印。

CAMPARY 有很多缺点(未启用、不受支持、未维护)。但是对于在 CUDA 上需要 mp 数字的人(特别是如果你需要 sqrt),这是我找到的最佳选择。

于 2019-03-15T21:25:12.763 回答