科学计算一直是C++和其他计算机语言的一个重要应用领域。现实生活中数学随处可见,而计算机本质上就是对大量数字的运算。从纯数学的角度看,程序也不过是一个非常大的整数而已。<br /> C++标准涵盖了C所定义的数学运算函数,标准库还提供了复数complex和数值序列valarray,但对于近代数学来说这些工具还很不够。<br /> Boost程序库针对标准库的不足提供了有益的补充,增加了C99标准中引入的大量特殊数学函数和复数函数,还有四元数、八元数、拉格朗日多项式、贝塞尔曲线等许多扩充,几乎覆盖了近现代数学的大部分领域,包括高等代数、线性代数和统计学,大大增强了C++语言用于科学计算和理论研究的实用性和能力。再配合自身简洁而富于表现力的语法,C++完全可以在专业计算领域与Fortran一较高下。<br /> 本章介绍Boost数学领域的六个库:math.constants、integer、rational、ratio、crc和random。
9.1 math.constants
math.constants库提供了π、e、√2等常用的数学常数,支持float、double、longdouble精度,而且还支持自定义类型以获得更高的精度。<br /> math.constants库位于名字空间boost::math,需要包含头文件<boost/math/constants/constants.hpp>。
#include <boost/math/constants/constants.hpp>
using namespace boost::math;
9.1.1 基本用法
math.constants库定义有很多科学计算中用到的常数,精度高达小数点后100位,对于我们来说比较常用的有:
- pi : 圆周率π
- e : 自然对数的底
- root_two :√2
- root_three :√3
ln_two :ln2
此外还有其他基于这些基本常数的变化形式,如π/2、2π、πe、1/√2、sin(1)等等,读者可以参考Boost文档或者源代码。这些值都是编译期常数,没有运行时开销,所以运算效率很高。
为了方便使用,math.constants库在名字空间boost::math里又定义了三个子名字空间,分别是float_constants、double_constants和long_double_constants,我们可以直接使用这些名字空间里对应精度的常数。
示范这些数学常数用法的代码如下: ```cpp cout << setprecision(64); // 设置显示精度为64 位
auto a = float_constants::pi 2 2; // float精度的π cout << “area = “ << a << endl;
using namespace double_constants; // 使用double 精度
auto x = root_two root_three; // √2√3 cout << “root 2 * 3 = “ << x << endl;
cout << “root pi = “ << root_pi << endl; // √π cout << “pi pow e = “ << pi_pow_e << endl; // π^e / 程序的运行结果是: area = 12.56637096405029296875 root 2 3 = 2.449489742783178325424842114443890750408172607421875 root pi = 1.7724538509055161039640324815991334617137908935546875 pi pow e = 22.459157718361044686616878607310354709625244140625 */
<a name="RlWWM"></a>
### 9.1.2 高级用法
math.constants库模仿C++14里的模板变量特性,在boost::math::constants里定义了同名的模板函数,可以不必使用名字空间直接指定数值类型。如果我们使用另一个数学库--高精度数字库boost.multiprecision,那么就可以获得比long double还要高的精确度。
```cpp
using namespace boost::math::constants;
assert(pi<float>() == float_constants::pi); // 模板函数获取π值
assert(pi<double>() == double_constants::pi); // double精度
// 使用boost.multiprecision库的高精度浮点数
typedef boost::multiprecision::cpp_dec_float_100 float_100;
cout << setprecision(100) // 设置精度为100 位
<< pi<float_100>() << endl; // 100位小数浮点数
/* 程序的运行结果是:
3.14159265358979323846264338327950288419716939937510582097494
4592307816406286208998628034825342117068
*/
9.2 integer
integer库提供了一组有关整数处理的头文件和类,具有良好的可移植性,让C++能够更方便、更准确、更容易地处理整数类型,它还包括编译期计算两个整数的最大值或最小值、编译期log2计算等功能。本章将介绍其中的三个组件。
9.2.1 整数特征
C++98标准在头文件<limits>里定义了模板类std::numeric_limits,它使用模板特化技术给出了int、double等数据类型的相关特性,如最大最小值、是否有符号等等。这些特性大部分是编译期的常量,但最大最小值函数却不是常量,只能在运行时使用,这在泛型编程时会带来一些不方便。<br />C++11标准修复了这个缺点,使用新的关键字constexpr令这两个函数成为编译期常量。而Boost库的integer_traits组件派生自std::numeric_limits,使用另一种方式达到了同样的目的。<br /> integer_traits位于名字空间boost,需要包含头文件<boost/integer_traits.hpp>,即:
#include <boost/integer_traits.hpp>
using namespace boost;
类摘要
integer_traits的类摘要如下:
template<class T>
class integer_traits: public std::numeric_limits<T>
{
public:
BOOST_STATIC_CONSTANT(bool, is_integral = false);
};
integer_traits的名字表明了它的功能:它是一个整数特征类。由于继承自std::numeric_limits,因此拥有std::numeric_limits的全部能力。此外,它运用了模板特化技术和宏BOOST_STATIC_CONSTANT(参见4.9.2节),为各种整数类型提供编译期的常量最大最小值。<br /> 例如,integer_traits针对short的特化版本如下(为示范而做了简化):
template<>
class integer_traits<short>: public std::numeric_limits<short>,
{
public:
BOOST_STATIC_CONSTANT(bool, is_integral = true);
BOOST_STATIC_CONSTANT(T, const_min = -32768);
BOOST_STATIC_CONSTANT(T, const_max = 32767);
};
integer_traits<short>为std::numeric_limits<short>增加了两个静态常量成员const_min和const_max,其他方面均与std::numeric_limits<short>相同。<br />**用法**<br /> integer_traits是std::numeric_limits的子类,因此任何可以使用std::numeric_limits的地方也都可以使用integer_traits,用来获取整数类型的相关信息。函数min()和max()也同样能够使用,但新增加的const_min和const_max这两个静态成员变量可以完全取代它们,并且能够用于编译期的泛型编程。<br /> 示范integer_traits用法的代码如下:
cout << integer_traits<int >::const_max << endl;
cout << integer_traits<bool>::const_min << endl;
cout << integer_traits<long>::is_signed << endl;
9.2.2 标准整数类型
由于C++98标准制定于1998年,因此它只能涵盖C89的内容,未能赶上1999C标准,不是新的C标准的超集。为弥补这个缺陷,头文件<boost/cstdint.hpp>基于1999年C标准中规定的<stdint.h>,为标准的整型数提供了精确的定义,而且如果编译器提供了<stdint.h>,那么就会自动包含它以保证兼容性。<br /><boost/cstdint.hpp>声明的所有整数类型都位于名字空间boost,直接包含它即可:
#include <boost/cstdint.hpp>
using namespace boost;
解说
整数类型的名字规则如下:
- xxx :表明该类型是否有正负符号。有符号数为int,无符号数是uint。
- yyy :表明该类型的特性。least 表示该类型至少具有#位的宽度(可能会比#宽);fast 表示该类型不仅具有least 的性质,而且是CPU 处理速度最快的类型;如果没有yyy 标识,则该类型是一个精确宽度的类型,其宽度恰好是#位。
: 表明该类型的宽度,以位为单位。
另外
用法
示范
uint8_t u8; // 一个简单的8位无符号整数, 相当于unsigned char
int_fast16_t i16; // 最快的有符号16 位整数
int_least32_t i32; // 至少有32位的有符号整数
uintmax_t um; // 编译器支持的最大无符号整数类型
u8 = 255;
i16 = 32000;
i32 = i16;
um = u8 + i16 + i32;
// 输出各整数类型的大小和值
cout << "u8 :" << sizeof(u8) << " v = "<< (short)u8 << endl;
cout << "i16 :" << sizeof(i16) << " v = "<< i16 << endl;
cout << "i32 :" << sizeof(i32) << " v = "<< i32 << endl;
cout << "um :" << sizeof(um) << " v = "<< um << endl;
//输出各整数类型的极值
cout << (short)numeric_limits<int8_t>::max() << endl;
cout << numeric_limits<uint_least16_t>::max() << endl;
cout << numeric_limits<int_fast32_t>::max() << endl;
cout << numeric_limits<intmax_t>::min() << endl;
/* 注意:在流输出int8_t类型时需转换为short型,否则流输出会认为是一个字符类型,输出其ASCII码,而不是数字. 程序的运行结果如下:
u8 :1 v = 255
i16 :8 v = 32000
i32 :4 v = 32000
um :8 v = 64255
127
65535
9223372036854775807
-9223372036854775808
*/
①有的读者可能对于这种_t后缀的整数类型不太适应,其实这只是遵循了C++标准对类型的定义惯例而已(后缀t表示type),如标准库中的size_t、wchar_t、ptrdiff_t等等,本书中的有些代码也使用了这种命名方式。
9.2.3 整数类型模板类
头文件<boost/integer.hpp>与<boost/cstdint.hpp>功能类似,也提供一系列的整数类型定义,但它不使用typedef的形式,而是用模板类,可以为程序员自动地选择最合适的整数类型。<br /> 头文件<boost/integer.hpp>定义的类型位于名字空间boost,只需要包含它即可:
#include <boost/integer.hpp>
using namespace boost;
类摘要
template< typename LeastInt >
struct int_fast_t {
typedef implementation_supplied fast;
};
int_fast_t的内置类型fast可以自动给出模板参数类型LeastInt相应的处理速度最快的整数类型。如果LeastInt已经是最快的整数,则返回LeastInt,例如:
#include <boost/type_index.hpp> // 使用type_index 库
typedef int_fast_t<char>::fast cfast; // char 类型的最快类型
cout << typeindex::type_id<cfast>().pretty_name() << endl;
typedef int_fast_t<int>::fast ifast; // int 类型的最快类型
cout << typeindex::type_id<ifast>().pretty_name() << endl;
typedef int_fast_t<uint16_t>::fast u16fast; // uint16 类型的最快类型
cout << typeindex::type_id<u16fast>().pretty_name() << endl;
/* 这段代码里我们使用了boost.type_index库,它能够获得更可读的类型名字,比std::typeid更好。程序的运行结果如下:
char
int
unsigned short
*/
另一组模板类使用指定的整数位数或数值作为模板参数,内置类型least返回支持的最小整数类型,fast返回最快的整数类型。这组类包括int_t、uint_t、int_max_value_t、int_min_value_t和uint_value_t,它们的类摘要如下:
template< int Bits >
struct int_t
{
typedef implementation_supplied least;
typedef int_fast_t<least>::fast fast;
};
template< int Bits >
struct uint_t
{
typedef implementation_supplied least;
typedef int_fast_t<least>::fast fast;
};
int_t和uint_t的模板参数指明需要的整数宽度(以bit为单位),不一定是8的整数倍,但必须是正整数,内部类型least和fast返回能容纳Bits位的最小和最快整数类型。
template< long MaxValue >
struct int_max_value_t
{
typedef implementation_supplied least;
typedef int_fast_t<least>::fast fast;
};
int_max_value_t的模板参数表明要容纳的最大整数数值,参数必须是一个正整数,内部类型least和fast返回能容纳MaxValue的最小和最快有符号整数类型。
template< long MinValue >
struct int_min_value_t
{
typedef implementation_supplied least;
typedef int_fast_t<least>::fast fast;
};
int_min_value_t与int_max_value_t类似,但它的模板参数表明要容纳的最小整数数值,参数必须是一个负整数,内部类型least和fast返回能容纳MinValue的最小和最快有符号整数类型。
template< unsigned long Value >
struct uint_value_t
{
typedef implementation_supplied least;
typedef int_fast_t<least>::fast fast;
}
uint_value_t与int_max_value_t类似,它的模板参数表明要容纳的最大整数数值,参数必须是一个正整数,内部类型least和fast返回能容纳Value的最小和最快无符号整数类型。<br />**用法**<br /> 整数类型模板类与普通的整数类型一样容易使用,只需要多写一点点模板类代码,它就会根据所需要的整数宽度或者处理的整数值自动为你选择最合适的整数类型,这无疑大大方便了程序员的工作。<br /> 由于整数类型模板类内仅有typedef,没有其他数据成员或成员函数,因此这些类不会产生任何运行时的开销(元计算发生在编译期),与使用内置整数类型或者Boost整数类型同样高效。<br /> 示范这些整数类型模板类用法的代码如下:
template<typename T>
string type_name() { // 函数对象,简化类型名的输出
return typeindex::type_id<T>().pretty_name();
}
format fmt("type:%s,size=%dbit\n"); // 一个format 对象
typedef uint_t<15>::fast u15; // 可容纳15 位的无符号最快整数
cout << fmt % type_name<u15>() % (sizeof(u15) * 8);
typedef int_max_value_t<32700>::fast i32700; // 可处理32700 的最快整数
cout << fmt % type_name<i32700>() % (sizeof(i32700) * 8);
typedef int_min_value_t<-33000>::fast i33000; // 可处理-33000 的最快整数
cout << fmt % type_name<i33000>() % (sizeof(33000) * 8);
typedef uint_value_t<33000>::fast u33000; // 可处理33000 的最快无符号整数
cout << fmt % type_name<u33000>() % (sizeof(u33000) * 8);
/* 代码中使用format库(参见5.2节)对输出进行格式化,运行结果如下:
type:unsigned short, size=16bit
type:short, size=16bit
type:int, size=32bit
type:unsigned short, size=16bit
*/
9.3 rational
科学计算的基础是数字运算,C++语言内建了int/float/double,分别用于支持有限精度的整数和实数(小数),C++标准库提供了complex,支持复数的概念。但是,数学中另一种非常重要的数被遗漏了,那就是有理数(分数)。<br /> boost.rational库实现了有理数,补充了C++的数字概念。它基于C++的内建整数类型,数字运算时没有精度损失,可以应用于金融财务等需要准确值的领域。①<br /> rational位于名字空间boost,需要包含头文件<boost/rational.hpp>,即:
#include <boost/rational.hpp>
using namespace boost;
① Boost另有一个库ratio,它实现了C++标准定义的编译期有理数,参见9.4节。
9.3.1 类摘要
rational 类的简要声明如下:
template<typename I>
class rational
{
public:
rational(); // 默认构造函数,值为零
rational(I n); // 整数构造,相当于 n/1
rational(I n, I d); // 分数构造(n/d)
rational& operator=(I n); // 赋值
rational& assign(I n, I d);
I numerator() const; // 获取内部的分子分母
I denominator() const;
rational& operator+= (const rational& r); // 算术操作符
... // 其他操作符重载
rational& operator+= (I i); // 与整数的算术运算
... // 其他操作符重载
const rational& operator++(); // 递增与递减
const rational& operator--();
bool operator!() const; // 逻辑非操作符
operator bool_type() const; // bool 类型转换
bool operator< (const rational& r) const; // 比较操作符
bool operator== (const rational& r) const;
bool operator< (I i) const; // 与整数进行比较
... // 其他比较操作符重载
};
rational类内部把有理数用“分子/分母”的形式保存,并运算时使用最大公约数、最小公倍数等操作加以恰当的规范化。<br /> 类似标准库的复数类,rational需要使用模板定义基本整数类型,如:
rational<int> pi(22, 7); // 圆周率的“约率”
9.3.2 创建与赋值
rational有三种形式的构造函数,默认构造函数(无参)创建一个值为零的有理数,单参的构造函数创建一个整数(即分母为1),双参数的构造函数创建一个经规范化的分数:
rational<int> a; // a=0
rational<int> b(20); // b=20
rational<int> c(31415, 10000); // c=3.1415
rational重载了operator=,可以直接从另一个整数赋值,或者使用成员函数assign()接受“分子/分母”形式的两个整数参数赋值:
rational<int> r; // r=0
r = 0x31; // r=0x31(十六进制) 或 r=49(十进制)
r.assign(7, 8); // r=7/8
因为有理数是分数,是以除法来表示的数,所以rational的分母不能是0,当构造、赋值或者其他操作导致发生除以0的情形时,rational会抛出bad_rational异常。但这种情况一般很少发生,只要稍微留意就可以避免。
