应该如何解释 GMP/MPFR 肢体? 背景:

问题描述

任意精度库 GMP 和 MPFR 使用堆分配的机器字大小的整数数组来存储构成高精度数字/尾数的四肢。

应该如何解释这个肢体数组以恢复任意精度的整数?换句话说:对于 N 个肢体,每个肢体都包含 B 位,我应该如何解释它们以恢复 N*B 位数?

肢体大小真的会影响内存中的表示吗(见下文)?如果是这样,这背后的原理是什么?


背景:

我编写了一个小程序来查看表示的内部,但我对所看到的内容感到困惑。肢体似乎按最高有效数字顺序排列,而肢体本身采用本机最低有效数字格式。当使用 32 位字和固定为 128 位的精度表示 64 位字 0xAAAABBBBCCCCDDDD 时,我看到:

% c++ limbs.cpp -lgmp -lmpfr -o limbs && ./limbs
ccccdddd|aaaabbbb|00000000|00000000
00000000|00000000|ccccdddd|aaaabbbb

这似乎意味着内存中的表示可以不能作为一串位读回以恢复任意精度数(例如,如果将其加载到支持N*B 大小的单词)。此外,这似乎也表明肢体大小会改变表示形式,因此在不知道使用哪个肢体大小对其进行序列化的情况下,我将无法反序列化一个数字。

这是我的测试程序(使用带有 __GMP_SHORT_LIMB 宏的 32 位肢体):

#define __GMP_SHORT_LIMB
#include <gmp.h>
#include <mpfr.h>

#include <iomanip>
#include <iostream>

constexpr int PRECISION = 128;

void PrintLimbs(mp_limb_t const *const limbs) {
  std::cout << std::hex;
  constexpr int NUM_LIMBS = PRECISION / (8 * sizeof(mp_limb_t));
  for (int i = 0; i < NUM_LIMBS; ++i) {
    std::cout << std::setfill('0') << std::setw(2 * sizeof(mp_limb_t)) << limbs[i];
    if (i < NUM_LIMBS - 1) {
      std::cout << "|";
    }
  }
  std::cout << "\n";
}

int main() {
  {  // GMP
    mpz_t num;
    mpz_init2(num,PRECISION);
    mpz_set_ui(num,0xAAAABBBBCCCCDDDD);
    PrintLimbs(num->_mp_d);
    mpz_clear(num);
  }
  {  // MPFR
    mpfr_t num;
    mpfr_init2(num,PRECISION);
    mpfr_set_ui(num,0xAAAABBBBCCCCDDDD,MPFR_RNDN);
    PrintLimbs(num->_mpfr_d);
    mpfr_clear(num);
  }
  return 0;
}

解决方法

对于字节表示重要的 3 件事:

  • 肢体大小取决于您的机器和所选的 ABI。实际尺寸还受到 指甲 的可选存在的影响(实验性特征,因此四肢不太可能有指甲)。 MPFR 不支持指甲的存在。
  • 内存中的肢体表示遵循机器的字节序。
  • 肢体首先存储最不重要的肢体(即小端)。

请注意,从最后两点来看,在同一台大端机器上,数组的字节表示将取决于肢体大小。

关于肢体数组的大小,取决于类型。比如GMP的mpn层,完全由用户自己处理。

对于MPFR,大小是从mpfr_t对象的精度推导出来的;如果精度不是limb bitsize的倍数,则尾随位总是设置为0。还要注意,分配的内存可能比实际使用的内存多,不能与数组的大小混淆;你可以忽略这个事实,因为未使用的数据总是在实际的肢体数组之后。

EDIT 关于基本原理:操作肢体而不是字节是出于速度原因。然后我想有两个原因选择了小端序来表示肢体数组。首先,它使基本运算(加法、减法、乘法)更容易实现并且可能更快。其次,这对于实现算术模 2^K 要好得多,特别是当 K 可能发生变化时。

,

它终于为我点击了。肢体大小不会影响内存中的表示。

GMP/MPFR 中的数据little-endian 格式一致存储,即使被解释为跨肢体的字节串也是如此。但是 x86 上的寄存器是大端的。

打印肢体时不一致的结果来自从记忆中读回单词时的解释方式。当加载到寄存器中时,内存会从 little-endian(如何存储在内存中)重新解释为 big-endian(如何存储在寄存器中)。

我修改了下面的示例,以显示实际上我们重新解释内存的字长如何影响内容的打印方式,因为无论是否输出都是相同的使用了 32 位或 64 位肢体:

#define __GMP_SHORT_LIMB
#include <gmp.h>
#include <mpfr.h>

#include <iomanip>
#include <iostream>
#include <cstdint>

constexpr int PRECISION = 128;

template <typename InterpretAs>
void PrintLimbs(mp_limb_t const *const limbs) {
  constexpr int LIMB_BITS = 8 * sizeof(InterpretAs); 
  constexpr int NUM_LIMBS = PRECISION / LIMB_BITS;
  std::cout << LIMB_BITS << "-bit: ";
  for (int i = 0; i < NUM_LIMBS; ++i) {
    const auto limb = reinterpret_cast<InterpretAs const *>(limbs)[i];
    for (int b = 0; b < LIMB_BITS; ++b) {
      if (b > 0 && b % 16 == 0) {
        std::cout << " ";
      }
      uint64_t bit = (limb >> (LIMB_BITS - 1 - b)) & 0x1; 
      std::cout << bit; 
    }
    if (i < NUM_LIMBS - 1) {
      std::cout << "|";
    }
  }
  std::cout << "\n";
}

int main() {
  uint64_t literal = 0b1111000000000000000000000000000000000000000000000000000000001001;
  {  // GMP
    mpz_t num;
    mpz_init2(num,PRECISION);
    mpz_set_ui(num,literal);
    std::cout << "GMP where limbs are interpreted as:\n";
    PrintLimbs<uint64_t>(num->_mp_d);
    PrintLimbs<uint32_t>(num->_mp_d);
    PrintLimbs<uint16_t>(num->_mp_d);
    mpz_clear(num);
  }
  {  // MPFR
    mpfr_t num;
    mpfr_init2(num,PRECISION);
    mpfr_set_ui(num,literal,MPFR_RNDN);
    std::cout << "MPFR where limbs are interpreted as:\n";
    PrintLimbs<uint64_t>(num->_mpfr_d);
    PrintLimbs<uint32_t>(num->_mpfr_d);
    PrintLimbs<uint16_t>(num->_mpfr_d);
    mpfr_clear(num);
  }
  return 0;
}

这个打印(不管肢体大小):

GMP where limbs are interpreted as:
64-bit: 1111000000000000 0000000000000000 0000000000000000 0000000000001001|0000000000000000 0000000000000000 0000000000000000 0000000000000000
32-bit: 0000000000000000 0000000000001001|1111000000000000 0000000000000000|0000000000000000 0000000000000000|0000000000000000 0000000000000000
16-bit: 0000000000001001|0000000000000000|0000000000000000|1111000000000000|0000000000000000|0000000000000000|0000000000000000|0000000000000000
MPFR where limbs are interpreted as:
64-bit: 0000000000000000 0000000000000000 0000000000000000 0000000000000000|1111000000000000 0000000000000000 0000000000000000 0000000000001001
32-bit: 0000000000000000 0000000000000000|0000000000000000 0000000000000000|0000000000000000 0000000000001001|1111000000000000 0000000000000000
16-bit: 0000000000000000|0000000000000000|0000000000000000|0000000000000000|0000000000001001|0000000000000000|0000000000000000|1111000000000000

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...