GMP 6.1.2
- last update 2022.08.21
使用GMP时,不要使用本文档未说明的函数、宏、数据类型,否则不保证你的程序不与未来版本的GMP兼容。
基础概念
头文件和库
使用GMP所需要的一切声明都汇集到了头文件gmp.h里,此头文件同时适用于C和C++。GMP中有些函数接受FILE*参数,只有当同时包含了stdio.h时,这些函数才是可用的。
#include <stdio.h>
#include <gmp.h>
类似的还有:stdarg.h对应可变参数函数(如gmp_vprintf()),obstack.h对应使用struct obstack参数的函数(gmp_obstack_prif())。
编译的时候要链接gmp库gcc prog.c -lgmp。C++函数单独存放在另外的库gmpxx中g++ prog.cpp -lgmpxx -lgmp。
数据类型和命名
整数(integer)是指高精度整数(multiple precision integer),数据类型mpz_t。
有理数(rational number)是指高精度分数(multiple precision fraction),类型mpq_t。
浮点数(Floating point number,Float)由一个任意精度的尾数(mantissa)和一个极限精度的指数(limited precision exponent)构成。
// 声明 mpz_t 类型的变量
mpz_t num;
mpz_t vec[20];
struct foo{mpz_t x,y;};
// 分数和浮点数
mpq_t quotient;
mpf_t fp;
浮点数函数用mp_exp_t类型接受和返回指数,此类型一般是long,有些机器上是int(为了效率)。
高精度数中等于一个机器字长的部分称为一个limb(汉语意思为肢,四肢的肢)。类型为mp_limb_t。通常一个limb是32位或64位。
一个高精度数包含的limb的数目,用mp_size_t类型保存,此类型一般般是long,某些系统上是int,将来可能是long long。
一个高精度数有的位数用mp_bitcnt_t类型保存,当前此类型总是unsigned long,将来某些系统上可能会变成unsigned long long。
随机状态用类型gmp_randstate_t表示。(Random state means an algorithm selection and current state data.)。
mp_bitcnt_t用于位(bit)的计数以及表示范围;size_t用于给字节和字符计数。
mpf_t 和 mpq_t 的区别?
一方面1/3可以用分数精确表示,但保存为浮点数会有误差。另一方面,实数域中,无理数无法表示为分数。
函数
函数分为六大类
- 整数函数:进行有符号整数运算,对应
mpz_t类型,函数名一律以mpz_为前缀;共有约150个。 - 有理数函数:给分数做运算,对应
mpq_t类型,函数名以mpq_为前缀,只有50个左有,但是有理数类型中的分子和分母可以分使用整数类型运算。 - 浮点数函数:进行浮点数运算,对应
mpf_t,以mpf_为前缀,共有70个左有。 - 底层函数:在自然数上运算,此类函数是供上述三类函数调用的,共有60个左有,特点是速度更快、特别难用;在特别注重效率的极其关键的代码中可以直接调用这些函数。
- 杂项函数:包括自定义内存分配函数和随机数生成函数两类。
关于变量的约定
GMP函数一般把输出参数放在输入参数之前,此约定是受赋值运算符的启发;兼容与BSD MP 的函数是例外:输出参数放在最后。
在一次函数调用中可以把同一个变量同时用作输入输出参数。gmz_mul(x,x,x);此调用计算整数x的平方然后把计算结果再存入x中。
使用GMP变量之前,需要先初始化,使用完毕要释放变量。初始化和释放是通过专门的函数调用完成的。一个变量只需要初始化一次,如果非要多次初始化,在每次初始化操作之间释放变量。初始化后的变量可以进行任意次赋值。
一般在函数的开头初始化变量,函数末尾释放变量;为了效率,应避免过多的变量初始化和释放操作。
void foo (void)
{
mpz_t n;
int i;
mpz_init (n);
for (i = 1; i < 100; i++)
{
mpz_mul (n, ...);
mpz_fdiv_q (n, ...);
...
}
mpz_clear (n);
}
关于参数的约定
把变量作为函数参数传递时,实际上传递的是指针。
如果自己编写的函数要返回结果,应该仿照GMP的库函数,使用输出参数;如果直接使用return语句,返回的仅仅是个指针。mpz_t实际上是长度为1的结构体数组,结构体内部的成员变量仅供库内部使用,如果在程序中访问了结构体成员,在新版GMP库中很可能不兼容。
内存分配
GMP自己会管理自己申请的内存。mpz_t和mpq_t在类型的变量需要时会申请更大的内存,但从不会减小内存,为了效率;mpf_t类型的变量使用固定的内存,内存大小根据初始化时的精度设置确定。如果程序要释放内存,可以清楚不再使用的GMP变量,或者mpz_realloc2函数释放内存。
GMP默认使用malloc系列函数进行内存分配,但这是可设置的。
重入
GMP是可重入的且是线程安全的,除了如下例外情况:
- 编译时使用了
--enable-alloca=malloc-notreentrant选项 - 函数
mpf_set_default_prec()和mpf_init()使用全局变量保存精度 - 函数
mpz_random()以及其它旧随机数生成函数使用全局变量保存随机状态,故是不可重入的。新的随机函数可用,这些函数使用gmp_randstate_t参数 gmp_randinit()(已过时)在全局变量中返回错误码,所以不可重入;使用新函数gmp_randinit_default()或gmp_randinit_lc_2exp()mp_set_memory_funcitons()使用全局变量保存内存分配函数- 如果
mp_set_memory_functions()设置的内存分配函数是不可重入的,那么GMP也将是不可重入的。默认的malloc()函数是不可冲入的 - 如果标准IO函数不可重入,则GMP的IO函数也不可重入
- 多个线程同时读一个GMP变量是安全的;但多线程一个读一个写、或多个同时写入都是不安全的;多线程使用同一个
gmp_randstate_t变量生成随机数也是不安全的
宏和常量
const int mp_bits_per_limb 一个limb的位数
const char * const gmp_version x.y.z 格式的GMP版本;例如“6.2.1”
GMP编译时用的编译器__GMP_CC
GMP编译时的选项__GMP_CFLAGS
5. 整数函数
初始化
void mpz_init(mpz_t x) 初始化x并设其初值为0
void mpz_inits(mpz_t x, ...) 初始化参数列表中的变量,初值设为0. 最后一个参数用NULL,表示结束。
void mpz_clear(mpz_t x), void mpz_clears(mpz_t x, ...) 释放变量
void mpz_realloc2(mpz_t x, mp_bitcnt_t n),把x的空间设为n位,如果新的空间合适,x的值保持不变,如果存不下,x的值设为零。一般不需要调用此函数。除非1. 要释放多余的空间;2. 一次性分配足够的空间以避免后续的计算多次逐步分配空间影响性能。
Change the space allocated for x to n bits. The value in x is preserved if it fits, or is set to 0 if not.
赋值
void mpz_set(mpz_t x, const mpz_t op)
void mpz_set_q(mpz_t x, const mpq_t op)
void mpz_set_f(mpz_t x, const mpf_t op)
void mpz_set_ui(mpz_t x, const unsigned long op)
void mpz_set_si(mpz_t x, const signed long op)
void mpz_set_d(mpz_t x, const double op)
把op的值赋值给x。
int mpz_set_str(mpz_t x, const char * s, int base)
把字符串代表的整数值赋值给x,忽略字符串中的空白字符。base的取值范围是 2~62。如果字符串是有效的数字,函数返回0,否则函数返回-1.
- 当base取值为0时,根据字符串的前缀确定基数:0x和0X代表16进制、0代表八进制、0b和0B表示二进制、否则就是十进制
- 当base <= 36 时,英文字符不区分大小写
- 当 37 <= base && base <=62 时,大写字母代表
10~35之间的数,小写字母代表61~61.
void mpz_swap(mpz_t x, mpz_t y)
交换x和y的值。
初始化和赋值组合
void mpz_init_set (mpz t rop, const mpz t op)
void mpz_init_set_ui (mpz t rop, unsigned long int op)
void mpz_init_set_si (mpz t rop, signed long int op)
void mpz_init_set_d (mpz t rop, double op) [Function]
Initialize rop with limb space and set the initial numeric value from op.
int mpz_init_set_str (mpz t rop, const char *str, int base)
Initialize rop and set its value like mpz_set_str (see its documentation above for details).
If the string is a correct base base number, the function returns 0; if an error occurs it returns
−1. rop is initialized even if an error occurs. (I.e., you have to call mpz_clear for it.)
……
转换函数
这些函数把GMP整数(mpz_t)转换成标准C语言类型。C类型转换到GMP整数,使用赋值函数和输出输出函数。
unsigned long mpz_get_ui(mpz_t op); // 小心溢出
signed long mpz_get_si(mpz_t op); // 小心溢出
double mpz_get_d(mpz_t op);
char *mpz_get_str(char *str, int base, mpz_t op); // 4)base 取值 2~62
// double mpz_get_d_2exp(signed long * exp, mpt_t op); // 类似于C的 frexp() 函数
这一类函数都有mpz_get_前缀,用si、ui、d、str对应signed long、unsigned long、double、char *str类型;返回值都是对应的结果类型。
转成整数、浮点数,如果超出了其表示范围,转换过来就没有意义了。可以用mpz_fit_ulong_p(mpz_t op)一族的函数判断一下,在取值范围内,则返回1,否则返回0.
对于函数4),base的取值范围是2~62,在2~36范围内,使用小写字母,-2~-36使用大写字母,37~62使用数字、大写字母、小写字母。
如果str是NULL,则函数自己申请一块内存存放结果,内存大小位strlen(str)+1,字符加上尾零。(那就需要我们自己手动释放喽)。
如果str不是NULL,那么它指向的空间至少应当是mpz_sizeinbase(op, base)+2,+2是为存储尾零和(可能的)符号位。
函数返回结果字符串。
数值计算函数
加、减、乘、取相反数、取绝对值。返回值都是void。add\sub\mul\neg\abs
除法运算单独列出。
void mpz_add(mpz_t rop, mpz_t x, mpz_t y) // x+y ⇒ rop
void mpz_sub(mpz_t rop, mpz_t x, mpz_t y) // x-y ⇒ rop
void mpz_mul(mpz_t rop, mpz_t x, mpz_t y) // x*y ⇒ rop
void mpz_neg(mpz_t rop, mpz_t op) // -op ⇒ rop
void mpz_abs(mpt_t rop, mpz_t op) // |op| ⇒ rop
而且加减乘都有与signed long和unsigned long对应的变体。
void mpz_add_ui(mpz_t rop, mpz_t x, unsigned long y) // x+y ⇒ rop
void mpz_sub_ui(mpz_t rop, mpz_t x, unsigned long y) // x-y ⇒ rop
void mpz_ui_sub(mpz_t rop, unsigned long x, mpz_t y) // x-y ⇒ rop // 留意,减法有两个变体。
void mpz_mul_ui(mpz_t rop, mpz_t x, unsigned long y) // x*y ⇒ rop
把ui换成si就是另外四个变体。
// 还有这四个函数
// addmul,submul,mul_2exp
除法函数
除法除不尽的时候要考虑舍入,GMP的整数除法运算有三种摄入模式:向上取整(ceil)、向下取整(floor)、向零取整(也叫截断,trunct),分别对应cidv,fdiv和tdiv。
基本的除法函数,返回值都是void。
void mpz_cdiv_q(mpz_t q, mpz_t n, mpz_t d)
void mpz_cdiv_r(mpz_t r, mpz_t n, mpz_t d)
void mpz_cdiv_qr(mpz_t q, mpz_t r, mpz_t n, mpz_t d)
其中,n是被除数,d是除数(divisor),q是商(quote),r是余数(remainder)。fdiv和tdiv的除法函数与之类同。
向上取整的除法,余数的符号和d相反;向下取整的符号,余数的符号和d相同;向零取整的除法,余数符号和被除数相同。
三种取整方式都满足n = d*q + r, |r| <= |d|。
q仅计算商,r仅计算余数,qr同时计算商和余数,qr函数中,q和r不能使用同一个变量,否则行为不定。
基本类型的变体有ui后缀(unsigned long)一种,ui指的是使用unsigned long作为除数。ui变体中 ,余数作为函数返回值返回,因为ui不能表示负数,所以返回的实际是余数的绝对值。
unsigned long mpz_cdiv_q(mpz_t q, mpz_t n, unsigned long d)
exp变体:(手册有一堆细节描述……)。
void mpz_cdiv_q/r/qr_2exp(mpz_t rop, mpz_t n, mpz_bitcnt_t b) // 除数是 2^b
还有好几个除法类别的函数……
幂函数
// base^exp ==> rop
void mpz_pow_ui(mpz_t rop, const mpz_t base, unsigned long exp)
void mpz_ui_pow_ui(mpz_t rop, unsigned long base, unsigned long exp)
// base^exp mod m => rop
void mpz_powm(mpz_t rop, const mpz_t base, const mpz_t exp, const mpz_t m);
void mpz_powm_ui(mpz_t rop, const mpz_t base, unsigned long exp, const mpz_t m);
求根函数
平方根,n次根号
数论函数
公约数、公倍数、阶乘、误差、……
比较函数
int mpz_cmp(const mpz_t op1, mpz_t op2)
int mpz_cmp_si(const mpz_t op1, signed long op2)
int mpz_cmp_ui(const mpz_t op1, unsigned long op2)
int mpz_cmp_d(const mpz_t op1, double op2)
op1 小于、等于、大于op2的时候分别返回小于零、等于零、大于零的数字。(没说-1,+1)。
而且si、ui、d三个变体实际是宏,所以会对操作数多次求值。
int mpz_cmpabs(const mpz_t op1, mpz_t op2)
// si、ui、d 三个变体
比较两个操作数的绝对值得大小。
int mpz_sgn(const mpz_t op); // 符号函数,负数返回-1,正数返回+1,零返回0.
逻辑和位运算函数
输入输出函数
- 向stio写入或从stdio中读取;如果传入NULL,则从stdout/stdin中读写。
- 最好在 gmp.h 前面引入 stdio.h 以便于GMP定义使用stdio的函数原型。
size_t mpz_inp/out_raw/str,
// 以字符串形式读写
size_t mpz_inp_str(mpz_t op, FILE* stream, int base) // 读取的时候忽略过开头的空白字符(如果有)
size_t mpz_out_str(FILE* stream, int base, const mpz_t op)
// 以二进制位的形式读写,读写正好匹配。写入的格式是:先写入四个字节的size,然后写入size个limbs。
// size和limbs的写入顺序都是大端序。
size_t mpz_inp_raw(mpz_t op, FILE *stream)
size_t mpz_out_raw(FILE*stream, const mpz_t op)
这四个函数都返回实际写入/读取的字节数,如果遇到错误,返回0。
这四个函数都不会自动换行,在stdou输出的时候得注意。
gmp_printf系列的函数也能实现上述读写功能。
随机数函数
老的函数不要用了,线程不安全。返回值都是void。
void mpz_urandomb(mpz_t op, gmp_randstate_t state, mp_bitcnt_t n) // [0, 2^n-1],均匀分布
void mpz_urandomm(mpz_t op, gmp_randstate_t state, mp_bitcnt_t n) // [0, m-1], 均匀分布
后面有单独一个章叙述随机数函数。
5.15 杂项函数
fits,odd/eve,sizeinbase
// 1. 如果合适,返回1,否则返回0
// {signed, unsigned} X {long,int,short}
int mpz_fits_ulong_p(const mpz_t op)
int mpz_fits_slong_p(const mpz_t op)
int mpz_fits_uint_p(const mpz_t op)
int mpz_fits_sint_p(const mpz_t op)
int mpz_fits_ushort_p(const mpz_t op)
int mpz_fits_sshort_p(const mpz_t op)
// 2. 是奇数/偶数则返回1,否则返回0
int mpz_odd_p(const mpz_top)
int mpz_even_p(const mpz_top)
// 3.在给定基数下数字的个数
size_t mpz_sizeinbase(const mpz_t op, int base)
5.16 特殊函数
一般场景用不到这些函数。
void _mpz_realloc(mpz_t op, mp_size_t new_alloc)
size_t mpz_size(const mpz_t op) // 返回op中limb的个数;op为零的时候,会返回0.
//其它八九个函数省略……
mpz_size使用实例
#include<stdio.h>
#include<stdint.h>
#include<gmp.h>
#include<limits.h>
int main(void)
{
mpz_t x;
mpz_init_set_ui(x, ULONG_MAX);
size_t sz = mpz_size(x);
printf("size:%zd\n", sz); // 1
mpz_realloc2(x, 10);
printf("size:%zu\n", sz); // 还是 1
return 0;
}
realloc函数
#include<stdio.h>
#include<stdint.h>
#include<gmp.h>
#include<limits.h>
void insp_it(mpz_t x){
printf("使用空间:%d\n", x->_mp_alloc);
printf("占据空间:%d\n", x->_mp_size);
}
int main(void)
{
mpz_t x;
mpz_init_set_ui(x, ULONG_MAX);
size_t sz = mpz_size(x);
//
printf("初始化\b");
insp_it(x);
printf("realloc2(x,10)\n");
mpz_realloc2(x, 120); // mpz_realloc2 按 位 计数
insp_it(x);
printf("realloc(x,10)\n");
mpz_realloc(x, 10); // mpz_realloc 以 limb 为单位计数,他是 _mpz_ralloc 的宏
insp_it(x);
return 0;
}
7 浮点数
加减乘除取余初始化、输入输出是不是和mpz类同。
把 mpz 换成 mpf,就得到一大批mpf的函数。
7.1 初始化
浮点数比整数多了一个属性:小数精度。精度用有效数字的二进制位数表示。
// 设置/读取默认精度
void mpf_set_default_prec(mp_bitcnt_t prec)
mp_bitcnt_t get_default_prec(void)
设置默认精度后,初始化浮点变量,如果没有指定精度,则使用此默认精度。之前已经初始化好的变量不受此影响。
Set the default precision to be at least prec bits. All subsequent calls to mpf_init will use
this precision, but previously initialized variables are unaffected.
输入输出
函数名,stdio里的格式化输入输出加 gmp_ 前缀就是了。
说明符,用
| 。 | 。 |
|---|---|
| Z | mpz_t |
| Q | mpq_t |
| F | mpf_t |
| M | mp_limb_t |
| N | mp_limm_t 的数组 |
mpz_t z;
gmp_printf ("%s is an mpz %Zd\n", "here", z);
mpq_t q;
gmp_printf ("a hex rational: %#40Qx\n", q);
mpf_t f;
int n;
gmp_printf ("fixed point mpf %.*Ff with %d digits\n", n, f, n);
mp_limb_t l;
gmp_printf ("limb %Mu\n", l);
const mp_limb_t *ptr;
mp_size_t size;
gmp_printf ("limb array %Nx\n", ptr, size);
ch09 随机数函数
9.1 初始化随即状态
void gmp_randinit_default(gmp_randstate_t state) // 使用默认算法初始化种子

本文详细介绍了GMP库的基础概念,包括头文件、数据类型和命名,特别是mpf_t和mpq_t的区别。文章还深入探讨了整数函数,包括初始化、赋值、转换函数和数值计算函数等,强调了正确使用GMP变量的重要性,并提醒避免使用未说明的函数以保持与未来版本的兼容性。
1061





