目录
第1章编译
1.1 简介
MPIR 是一个开源的多精度整数和有理数计算库,基于 GMP 库开发。它有什么用呢?
如下图所示,笔者在推导缓和曲线弦长随弧长变化的函数时,得到了一个递推公式。
图1.1
递推公式如下:
可见的计算只涉及到分数(有理数)的四则运算,是比较简单的。只是随着的增大,运算量越来越大,手工计算会越来越困难。
上述公式可以编程实现。C/C++的基本数据类型中,使用double表示是比较恰当的。double是有误差的,而且的计算误差会积累,随着的增大的计算误差会越来越大。
借助MPIR的有理数计算功能,可以解决上述问题。的前几项见下表:
n |
系数 |
0 |
1 |
1 |
-1/90 |
2 |
1/22680 |
3 |
-79/2043241200 |
4 |
2633/12504636144000 |
5 |
1215749/1646485441080480000 |
6 |
48316241/15905049360837436800000 |
7 |
193921367/49059847710292202784000000 |
8 |
-73267589502073/927478383356982505319631360000000 |
MPIR是从GMP移植而来的。因为GMP主要是针对于Unix、Linux操作系统的,使用VC++编译比较困难。MPIR的主要工作其实就是把GMP移植到Windows操作系统,使得VC++能够编译。
1.2 下载
浏览网址http://www.mpir.org/,下载mpir-2.7.0.zip,然后解压到W:mpirv2.7.0mpir。目录结构如下图所示:
图1.2
1.3 解决方案
mpir 2.7.0自带了四个Visual Studio 解决方案,如下所示:
W:mpirv2.7.0mpiruild.vc10mpir.sln 使用vc2010打开
W:mpirv2.7.0mpiruild.vc11mpir.sln 使用vc2012打开
W:mpirv2.7.0mpiruild.vc12mpir.sln 使用vc2013打开
W:mpirv2.7.0mpiruild.vc14mpir.sln 使用vc2015打开
使用vc2010打开mpir.sln后,可以看到有七个项目,如下图所示:
图1.3
这七个项目的区别如下:
dll_mpir_gc 生成动态库,不使用汇编代码
dll_mpir_p3 生成动态库,含32位汇编代码(奔腾3)
dll_mpir_core2 生成动态库,含64位汇编代码(酷睿2)
lib_mpir_cxx 生成静态库,不使用汇编代码
lib_mpir_gc 生成静态库,不使用汇编代码
lib_mpir_p3 生成静态库,含32位汇编代码(奔腾3)
lib_mpir_core2 生成静态库,含64位汇编代码(酷睿2)
mpir有两套API:C API和C++ API。前者可用于C和C++,后者只能用于C++。lib_mpir_gc、lib_mpir_p3、lib_mpir_core2只含有C API;lib_mpir_cxx只含有C++ API;dll_mpir_gc、dll_mpir_p3、dll_mpir_core2既有C API又有C++ API。
从兼容性考虑,建议不要使用汇编代码。只有在对速度要求特别高的情况下,需要考虑使用汇编代码。
1.4 创建项目
为了使用vc6~vc2008编译mpir 2.7.0,需要创建项目。
笔者在W:mpirv2.7.0vc目录下创建了11个目录,如下图所示。
make-dll-c 将创建动态库,只使用c语言
make-dll-asm32 将创建动态库,使用32位汇编代码
make-dll-asm64 将创建动态库,使用64位汇编代码
make-libS-* 将创建静态库,C运行时库为单线程
make-libT-* 将创建静态库,C运行时库为多线程
make-libD-* 将创建静态库,C运行时库为多线程DLL
图1.4
每个目录下,又有多个子目录,如下图所示。如:使用vc6编译请进入vc6目录。
图1.5
1.5 复制文件树
W:mpirv2.7.0mpiruild.vc10mpir.sln里每个项目都有文件树。如下图所示。现在要把这些项目的文件树复制到上一节新建的项目里。
图1.6
可以手工添加,只不过五百多个文件添加起来还是很繁琐的。可以使用vcHelper来完成此项繁琐的工作。如下图所示,vcHelper把W:mpirv2.7.0mpiruild.vc10dll_mpir_gcdll_mpir_gc.vcxproj里的文件树复制到W:mpirv2.7.0vcmake-dll-cvc6mpir.dsp。
图1.7
需要复制的文件树如下表所示:
从 W:mpirv2.7.0mpiruild.vc10dll_mpir_gcdll_mpir_gc.vcxproj 复制到 W:mpirv2.7.0vcmake-dll-cvc6mpir.dsp W:mpirv2.7.0vcmake-libD-cvc6mpirD.dsp W:mpirv2.7.0vcmake-libS-cvc6mpirS.dsp W:mpirv2.7.0vcmake-libT-cvc6mpirT.dsp |
从 W:mpirv2.7.0mpiruild.vc10dll_mpir_p3dll_mpir_p3.vcxproj 复制到 W:mpirv2.7.0vcmake-dll-asm32vc6mpir.dsp W:mpirv2.7.0vcmake-libD-asm32vc6mpirD.dsp W:mpirv2.7.0vcmake-libS-asm32vc6mpirS.dsp W:mpirv2.7.0vcmake-libT-asm32vc6mpirT.dsp |
从 W:mpirv2.7.0mpiruild.vc10dll_mpir_core2dll_mpir_core2.vcxproj 复制到 W:mpirv2.7.0vcmake-dll-asm64vc2008mpir.dsp W:mpirv2.7.0vcmake-libD-asm64vc2008mpirD.dsp W:mpirv2.7.0vcmake-libS-asm64vc2008mpirS.dsp W:mpirv2.7.0vcmake-libT-asm64vc2008mpirT.dsp |
vcHelper的下载:访问http://pan.baidu.com/s/1gd7XDkf 然后进入publicToolsvcHelper下载压缩包。
1.6 不使用预编译头文件
设置VC++,编译时不使用预编译头文件。
VC++6.0的设置如下图所示:
图1.8
VC++.NET的设置如下图所示:
图1.9
1.7 包含目录
编译.c文件时,需要文件W:mpirv2.7.0mpirmpir.h。该文件相对于VC++6.0项目文件(W:mpirv2.7.0vcmake-dll-cvc6mpir.dsp)的相对路径为../../../mpir
VC++6.0的具体配置如下:
图1.10
VC++.NET的配置如下
图1.11
1.8 定义宏
需要定义宏:HAVE_CONFIG_H,不过它似乎没有什么实质作用。
编译生成动态库,还需要定义宏MSC_BUILD_DLL。它用来导出mpir的API函数。
VC++6.0的配置如下:
图1.12
VC++.NET的配置如下。vc2002~vc2008不能选择All Configuration,vc2010后可以选择All Configuration了。
图1.13
1.9 编译前事件
编译前,需要执行"编译前事件"。下图是VC++.NET的配置:
图1.14
上图表示编译前将执行两条命令:
"cd ......mpiruild.vc10"表示设置......mpiruild.vc10为当前目录。这是相对于W:mpirv2.7.0vcmake-dll-cvc6mpir.dsp的目录,换成绝对目录就是W:mpirv2.7.0mpiruild.vc10
"prebuild gc Win32"表示运行当前目录下的prebuild.bat文件,也就是运行W:mpirv2.7.0mpiruild.vc10prebuild.bat。gc和Win32是传递给prebuild.bat的两个参数。第一个参数有三个选项:gc、p3、core2,分别表示不使用汇编代码、使用32位汇编代码、使用64位汇编代码。第二个参数有两个选项:Win32、x64,分别表示32位、64位平台。
VC++6.0不支持"编译前事件",可以设置gmp-h.in文件的Custom Build,如下图所示:
图1.15
编译时,只要gmp-h.in是第一个被编译的,那么"cd ......mpiruild.vc10"和"prebuild gc Win32"就会被执行,相当于"编译前事件"。
prebuild.bat的内容有些复杂,笔者并没有完全理解,只知道"prebuild.bat gc Win32"执行后将在W:mpirv2.7.0mpir目录下生成五个文件:config.h、gmp.h、gmp-mparam.h、longlong.h、mpir.h。这些都是编译时需要的文件。
1.10 修改 obj 的位置
mpf、mpn、mpq、mpz下有很多重名的.c文件,编译时它们将生成重名的.obj文件。这些重名的.obj文件不能放在同一目录下,因此需要修改mpf、mpn、mpq、mpz这四个目录下所有.c文件生成的.obj文件的存放目录。
对于VC++6.0,设置如下图所示。mpf目录下的.c文件生成的.obj文件,原存放目录为 TempDU,现存放目录为TempDUmpf。
图1.16
对于VC++.NET,设置如下图所示。mpf目录下的.c文件生成的.obj文件,原存放目录为$(IntDir),现存放目录为$(IntDir)mpf。注意:$(IntDir)mpf最后的一定不能少,因为$(IntDir)mpf会被当做文件,而$(IntDir)mpf才会被当做目录。
图1.17
根据上面两张图的配置,再修改mpn、mpq、mpz目录下.c文件的配置。
1.11 编译yasm
不需要编译汇编代码的,请跳过本节。
编译汇编代码需要yasm。mpir 2.7.0自带了yasm源代码,如下所示:
W:mpirv2.7.0mpiryasmMkfilesvc9yasm.sln 使用vc2008编译
W:mpirv2.7.0mpiryasmMkfilesvc10yasm.sln 使用vc2010编译
如果需要最新版本的yasm,可到如下网址下载:
http://www.tortall.net/projects/yasm/
http://yasm.tortall.net/
使用vc2010打开W:mpirv2.7.0mpiryasmMkfilesvc10yasm.sln。选择Win32 Release,然后鼠标右键单击"vsyasm"在弹出菜单中单击【Rebuild】。如下图所示。
编译时将会执行Python脚本文件W:mpirv2.7.0mpiryasmmodulesarchx86gen_x86_insn.py。笔者不清楚该脚本做了哪些工作,经测试发现不执行该脚本也不会影响最终exe文件的生成。
图1.18
编译生成的文件是W:mpirv2.7.0mpiryasmMkfilesvc10Win32Releasevsyasm.exe。VC++可以调用该程序编译汇编代码。
1.12 编译汇编代码
不需要编译汇编代码的,请跳过本节。
VC++调用yasm编译汇编代码有三种方法:Custom Build、.rules文件、.targets文件。
1.12.1 Custom Build
Custom Build的优点是适用面广,vc6~vc2015都能使用;缺点是修改命令行参数稍显麻烦。
下图是mpnyasm*.asm的Custom Build配置:
图1.19
Outputs是编译*.asm后的输出文件,连接时会用到此文件。上图的设置为"$(IntDir)$(InputName).obj"其中$(IntDir)是编译时的临时目录,$(InputName)是编译.asm文件名,如:编译add_n.asm时,$(InputName)就是add_n。
Commands是vsyasm.exe的命令行,具体如下:
......mpiryasmMkfilesvc10Win32Releasevsyasm.exe -Xvc -f Win32 -g cv8 -d "DLL" -o "$(IntDir)\" -rnasm -pnasm $(InputPath) |
......mpiryasmMkfilesvc10Win32Releasevsyasm.exe是W:mpirv2.7.0mpiryasmMkfilesvc10Win32Releasevsyasm.exe相对于W:mpirv2.7.0vcmake-dll-asm32vc6mpir.dsp的相对路径。
-Xvc 是错误信息显示格式。对于gcc编译器可设置为 -Xgcc 或 -Xgnu。
-f Win32 表明是32位平台,对于64位平台可指定为-f x64
-g cv8 用来生成调试信息,这种调试信息不能被 vc6 识别,所以使用vc6编译.asm文件时,请去除该选项。
-o "$(IntDir)\"表示将在目录$(IntDir)里生成.obj文件。注意$(IntDir)后面的两个反斜杠一个都不能少。
$(InputPath)表示输入文件,如:add_n.asm、addmul_1.asm……
这里需要说明一下vc6里Custom Build的编译顺序。因为vc6不支持"编译前事件",可使用Custom Build代替"编译前事件",此时编译顺序就显得非常重要了。笔者经实验得到以下规律(并不能确保它一定正确):
1、Custom Build比.c或.cpp的编译要早;
2、在IDE里先后设置文件A、B的Custom Build,则编译时的顺序为逆序,即先编译B再编译A;
3、初次打开.dsp文件,编译顺序是文件在.dsp里的相反顺序。如:.dsp文件里从上到下依次有A、B需要Custom Build,那么编译时先编译B再编译A。
在图1.15中,gmp-h.in在.dsp文件里是最后一个文件,因此Custom Build时它总是第一个被编译。这样,就保证了它扮演"编译前事件"的角色。
1.12.2 .rules文件
vc2005和vc2008可以使用. rules文件。使用vc2008打开W:mpirv2.7.0vcmake-dll-asm32vc2008mpir.sln,鼠标右键单击mpir项目,弹出菜单中单击【Custom Build Rules...】菜单项
图1.20
弹出如下界面,请单击"Find Existing..."按钮。
图1.21
请载入W:mpirv2.7.0mpiryasmMkfilesvc9yasm.rules文件,如下图所示:
图1.22
下图所示界面,请单击"否"按钮。
图1.23
下图所示界面内,请勾选"Yasm",然后单击"OK"按钮。
图1.24
此时查看mpir项目属性,可以看到配置里多了一项"Yasm Assembler",可以在这里对Yasm的命令行参数进行配置。如下图所示:
图1.25
再查看某个.asm文件的属性,可以看到它被自动关联到Yasm Assembler,也就是说编译.asm文件时将使用Yasm Assembler来编译。如下图所示:
图1.26
1.12.3 .targets文件
从vc2010开始. rules文件被.targets文件代替。使用vc2010打开W:mpirv2.7.0vcmake-dll-asm32vc2010mpir.sln,鼠标右键单击mpir项目,弹出菜单中单击【Build Customization...】菜单项
图1.27
同样是单击"Find Existing..."按钮
图1.28
请载入W:mpirv2.7.0mpiryasmMkfilesvc10vsyasm.targets文件,如下图所示:
图1.29
下图所示界面,请单击"否"按钮。
图1.30
下图所示界面内,请勾选"vsyasm",然后单击"OK"按钮。
图1.31
注意:Yasm Assembler不会自动关联*.asm,需要将*.asm的Item Type属性更改为Yasm Assembler。
图1.32
1.13 替换long long
VC++6.0不支持long long,可用__int64替换它。具体如下:
1、替换 gmp-impl.h 里的 long long int 为 __int64;
修改后的两行代码如下:
typedef __int64 DItype; typedef unsigned __int64 UDItype; |
2、替换printfdoprnt.c、printf epl-vsnprintf.c、scanfdoscan.c里的long long 为 __int64。
1.14 strnlen函数
vc6、vc2002、vc2003不支持strnlen函数,所以需要修改代码。具体就是修改printf epl-vsnprintf.c
修改前 |
修改后 |
#if ! HAVE_STRNLEN static size_t strnlen (const char *s, size_t n) { size_t i; for (i = 0; i < n; i++) if (s[i] == ' ') break; return i; } #endif |
#if _MSC_VER <= 1310 static size_t strnlen(const char*str ,size_t num) { size_t n = 0; if(str && num) { while(str[n]) { if(++n >= num) { break; } } } return n; } #endif//#if _MSC_VER <= 1310 |
vc6、vc2002、vc2003的_MSC_VER分别为1200、1300、1310。从vc2005开始,VC++实现了strnlen函数。