Boost C++ 库

...世界上最受尊敬和专家设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

区间算术库

本页内容
简介
概要
模板类 interval
运算和函数
区间支持库
常见陷阱和危险
基本原理
历史和致谢
与此页面相关的其他页面
舍入策略
检查策略
策略操作
比较
基数类型要求
选择您自己的区间类型
测试和示例程序
头文件包含
待办事项列表中的一些项目

简介和概述

顾名思义,该库旨在帮助操作数学区间。它由单个头文件 <boost/numeric/interval.hpp> 以及一个主要类型组成,该类型可以用作 interval<T>。实际上,此区间模板声明为 interval<T,Policies>,其中 Policies 是一个策略类,用于控制区间类的各种行为;interval<T> 只是为类型 T 选择默认策略。

警告! 并非所有处理器、操作系统和编译器的组合都支持原生浮点格式的保证区间算术。以下是已知在使用 interval<float>interval<double> 进行基本算术运算时能够正常工作的系统列表。

以上列表并非详尽无遗。即使系统没有为硬件浮点类型提供保证的计算,区间库仍然可以与用户定义的类型一起使用,并用于进行盒式算术。

区间算术

区间是一对数字,表示这两个数字之间的所有数字。(区间被认为是闭合的,因此包括边界。)该库的目的是将通常的算术函数扩展到区间。这些区间将写成 [a,b] 来表示 ab 之间的所有数字(包括 ab)。ab 可以是无穷大(但它们不能是相同的无穷大),并且 ab

区间算术的基本属性是 _**包含属性**_

“如果 _f_ 是一个定义在数字集合上的函数,_f_ 可以扩展到一个定义在区间上的新函数。这个新函数 _f_ 接受一个区间参数并返回一个区间结果,例如:∀ _x_ ∈ [ _a_,_b_], _f_(_x_) ∈ _f_([_a_,_b_]).”。

这样的属性不限于只有一个参数的函数。只要有可能,区间结果应该是能够满足该属性的最小区间(如果新函数总是回答 [-∞,+∞],则它不是真正有用的)。

用户想要使用此库的原因至少有两个。显而易见的原因是当用户必须使用区间进行计算时。一个例子是当输入数据具有一些内置的不精确性时:输入变量可以作为区间传递,而不是数字。另一个示例应用程序是求解方程,方法是将区间二等分,直到区间宽度足够小。第三个示例应用程序是计算机图形学,其中使用盒子、线段或射线的计算可以通过区间简化为使用点的计算。

使用区间算术的另一个常见原因是当计算机无法产生精确结果时:通过使用区间,可以量化舍入误差的传播。这种方法经常用于数值计算。例如,假设计算机存储的数字具有十个十进制有效数字。对于问题 1 + 1E-100 - 1,计算机将回答 0,尽管正确答案应该是 1E-100。借助区间算术,计算机将回答 [0,1E-9]。对于如此小的结果来说,这是一个相当大的区间,但现在已知精度,而无需计算误差传播。

数字、舍入和异常行为

_**基数类型**_是保存区间边界值的类型。为了成功使用区间算术,基数类型必须呈现一些特性。首先,根据区间的定义,基数必须是完全有序的,因此,例如,complex<T> 不能用作区间的基数类型。基数类型的数学函数也应该与全序兼容(例如,如果 x>y 且 z>t,那么也应该成立 x+z > y+t),因此模类型也不能使用。

其次,如果我们想要保证包含属性,计算必须是精确的,或者如果我们想要保证包含属性,则必须提供一些舍入方法(例如,朝向负无穷大或正无穷大)。请注意,我们也可以显式地指定不舍入,例如,如果基数类型是精确的,即数学运算的结果始终在不损失精度的情况下进行计算和表示。如果数字类型不精确,我们仍然可以显式地指定不舍入,其明显的后果是包含属性不再得到保证。

最后,由于严重的精度损失总是可能的,一些数字必须表示无穷大或必须定义异常行为。同样的情况也发生在 NaN(_非数字_)上。

鉴于所有这些,人们可能希望将类模板 interval 的模板参数 T 限制为 IEEE-754 标准定义的浮点类型 floatdoublelong double。实际上,如果区间算术旨在取代处理器浮点单元提供的算术,则这些类型是最佳选择。然而,与 std::complex 不同,我们不想将 T 限制为这些类型。这就是为什么我们允许舍入和异常行为由两个策略(舍入和检查)给出。然而,我们确实为上述浮点类型提供了高度优化的舍入和检查类特化。

运算和函数

在包含属性的指导下,定义区间上的基本算术运算很简单。例如,如果 [a,b] 和 [c,d] 是区间,则可以通过取包含 [a,b] 中所有数字 x 和 [c,d] 中所有数字 y 的所有数字 x+y 的最小区间来计算 [a,b]+[c,d];在这种情况下,将 a+c 向下舍入并将 b+d 向上舍入就足够了。其他运算符和函数的定义类似(请参阅下面的定义)。

比较

也可以定义一些比较运算符。给定两个区间,结果是一个三态布尔类型 { _false_, _true_, _indeterminate_ }。答案 _false_ 和 _true_ 很容易操作,因为它们可以直接映射到布尔值 _true_ 和 _false_。但对于答案 _indeterminate_ 并非如此,因为比较运算符应该是布尔函数。那么,为了获得布尔答案,该怎么办?

一种解决方案是决定采用异常行为,例如失败的断言或引发异常。在这种情况下,当结果不确定时将触发异常行为。

另一种解决方案是将 _indeterminate_ 始终映射到 _false_ 或始终映射到 _true_。如果选择 _false_,则比较将被称为“_确定_”;实际上,[ _a_,_b_] < [ _c_,_d_] 的结果将是 _true_ 当且仅当:∀ _x_ ∈ [ _a_,_b_] ∀ _y_ ∈ [ _c_,_d_], _x_ < _y_。如果选择 _true_,则比较将被称为“_可能_”;实际上,[ _a_,_b_] < [ _c_,_d_] 的结果将是 _true_ 当且仅当:∃ _x_ ∈ [ _a_,_b_] ∃ _y_ ∈ [ _c_,_d_], _x_ < _y_。

由于这些解决方案中的任何一个都有明确定义的语义,因此不清楚我们是否应该强制执行其中任何一个。因此,默认行为是通过在不确定情况下抛出异常来模拟实际比较。可以通过使用特定的比较命名空间来选择其他行为。还有一堆显式命名的比较函数。有关更多详细信息,请参阅比较页面。

库概述和用法

该库提供了两个截然不同的使用级别。一种是使用基本类模板 interval<T> 而不指定策略。这只需要了解并理解上面开发的概念和命名空间 boost 的内容。除了类 interval<T> 之外,此级别的用法还提供算术运算符(+-*/)、代数和分段代数函数(abssquaresqrtpow)、超越函数和三角函数(explogsincostanasinacosatansinhcoshtanhasinhacoshatanh)以及标准比较运算符(<<=>>===!=),以及几个特定于区间的函数(minmax,它们的含义与 std::minstd::max 不同;lowerupperwidthmedianemptysingletonequalinzero_insubsetproper_subsetoverlapintersecthullbisect)。

对于某些采用多个 interval<T> 类型参数的函数,会考虑包含至少一个 interval<T> 的参数类型 Tinterval<T> 的所有组合,以避免将 T 类型的参数转换为 interval<T> 类型的单例。这样做是出于效率原因(参数是单例的事实有时会使某些测试变得不必要)。

该库更高级的用法是手动选择策略 RoundingChecking,并通过使用 Policies := boost::numeric::interval_lib::policies<Rounding,Checking> 将它们传递给 interval<T, Policies>。可以通过使用区间支持库部分中详述的命名空间 boost::numeric::interval_lib 中提供的各种类来构造适当的策略。还可以通过命名空间重载运算符来选择比较方案。

概要

namespace boost {
namespace numeric {

namespace interval_lib {

/* this declaration is necessary for the declaration of interval */
template <class T> struct default_policies;

/* ... ; the full synopsis of namespace interval_lib can be found here */

} // namespace interval_lib


/* template interval_policies; class definition can be found here */
template<class Rounding, class Checking>
struct interval_policies;

/* template class interval; class definition can be found here */
template<class T, class Policies = typename interval_lib::default_policies<T>::type > class interval;

/* arithmetic operators involving intervals */
template <class T, class Policies>  interval<T, Policies> operator+(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> operator-(const interval<T, Policies>& x);

template <class T, class Policies>  interval<T, Policies> operator+(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> operator+(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> operator+(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  interval<T, Policies> operator-(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> operator-(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> operator-(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  interval<T, Policies> operator*(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> operator*(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> operator*(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  interval<T, Policies> operator/(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> operator/(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> operator/(const T& r, const interval<T, Policies>& x);

/* algebraic functions: sqrt, abs, square, pow, nth_root */
template <class T, class Policies>  interval<T, Policies> abs(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> sqrt(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> square(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> pow(const interval<T, Policies>& x, int y);
template <class T, class Policies>  interval<T, Policies> nth_root(const interval<T, Policies>& x, int y);

/* transcendental functions: exp, log */
template <class T, class Policies>  interval<T, Policies> exp(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> log(const interval<T, Policies>& x);

/* fmod, for trigonometric function argument reduction (see below) */
template <class T, class Policies>  interval<T, Policies> fmod(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> fmod(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> fmod(const T& x, const interval<T, Policies>& y);

/* trigonometric functions */
template <class T, class Policies>  interval<T, Policies> sin(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> cos(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> tan(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> asin(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> acos(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> atan(const interval<T, Policies>& x);

/* hyperbolic trigonometric functions */
template <class T, class Policies>  interval<T, Policies> sinh(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> cosh(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> tanh(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> asinh(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> acosh(const interval<T, Policies>& x);
template <class T, class Policies>  interval<T, Policies> atanh(const interval<T, Policies>& x);

/* min, max external functions (NOT std::min/max, see below) */
template <class T, class Policies>  interval<T, Policies> max(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> max(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> max(const T& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> min(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> min(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> min(const T& x, const interval<T, Policies>& y);

/* bounds-related interval functions */
template <class T, class Policies>  T lower(const interval<T, Policies>& x);
template <class T, class Policies>  T upper(const interval<T, Policies>& x);
template <class T, class Policies>  T width(const interval<T, Policies>& x);
template <class T, class Policies>  T median(const interval<T, Policies>& x);
template <class T, class Policies>  T norm(const interval<T, Policies>& x);

/* bounds-related interval functions */
template <class T, class Policies>  bool empty(const interval<T, Policies>& b);
template <class T, class Policies>  bool singleton(const interval<T, Policies>& x);
template <class T, class Policies>  bool equal(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool in(const T& r, const interval<T, Policies>& b);
template <class T, class Policies>  bool zero_in(const interval<T, Policies>& b);
template <class T, class Policies>  bool subset(const interval<T, Policies>& a, const interval<T, Policies>& b);
template <class T, class Policies>  bool proper_subset(const interval<T, Policies>& a, const interval<T, Policies>& b);
template <class T, class Policies>  bool overlap(const interval<T, Policies>& x, const interval<T, Policies>& y);

/* set manipulation interval functions */
template <class T, class Policies>  interval<T, Policies> intersect(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> hull(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> hull(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  interval<T, Policies> hull(const T& x, const interval<T, Policies>& y);
template <class T, class Policies>  interval<T, Policies> hull(const T& x, const T& y);
template <class T, class Policies>  std::pair<interval<T, Policies>, interval<T, Policies> > bisect(const interval<T, Policies>& x);

/* interval comparison operators */
template<class T, class Policies>  bool operator<(const interval<T, Policies>& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator<(const interval<T, Policies>& x, const T& y);
template<class T, class Policies>  bool operator<(const T& x, const interval<T, Policies>& y);

template<class T, class Policies>  bool operator<=(const interval<T, Policies>& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator<=(const interval<T, Policies>& x, const T& y);
template<class T, class Policies>  bool operator<=(const T& x, const interval<T, Policies>& y);

template<class T, class Policies>  bool operator>(const interval<T, Policies>& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator>(const interval<T, Policies>& x, const T& y);
template<class T, class Policies>  bool operator>(const T& x, const interval<T, Policies>& y);

template<class T, class Policies>  bool operator>=(const interval<T, Policies>& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator>=(const interval<T, Policies>& x, const T& y);
template<class T, class Policies>  bool operator>=(const T& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator==(const interval<T, Policies>& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator==(const interval<T, Policies>& x, const T& y);
template<class T, class Policies>  bool operator==(const T& x, const interval<T, Policies>& y);

template<class T, class Policies>  bool operator!=(const interval<T, Policies>& x, const interval<T, Policies>& y);
template<class T, class Policies>  bool operator!=(const interval<T, Policies>& x, const T& y);
template<class T, class Policies>  bool operator!=(const T& x, const interval<T, Policies>& y);

namespace interval_lib {

template<class T, class Policies>  interval<T, Policies> division_part1(const interval<T, Policies>& x, const interval<T, Policies& y, bool& b);
template<class T, class Policies>  interval<T, Policies> division_part2(const interval<T, Policies>& x, const interval<T, Policies& y, bool b = true);
template<class T, class Policies>  interval<T, Policies> multiplicative_inverse(const interval<T, Policies>& x);

template<class I>  I add(const typename I::base_type& x, const typename I::base_type& y);
template<class I>  I sub(const typename I::base_type& x, const typename I::base_type& y);
template<class I>  I mul(const typename I::base_type& x, const typename I::base_type& y);
template<class I>  I div(const typename I::base_type& x, const typename I::base_type& y);

} // namespace interval_lib

} // namespace numeric
} // namespace boost

模板类 interval

模板类 interval 本身的公共接口保持在最简单的最低限度
template <class T, class Policies = typename interval_lib::default_policies<T>::type>
class interval
{
  public:
  typedef T base_type;
  typedef Policies traits_type;

  interval();
  interval(T const &v);
  template<class T1> interval(T1 const &v);
  interval(T const &l, T const &u);
  template<class T1, class T2> interval(T1 const &l, T2 const &u);
  interval(interval<T, Policies> const &r);
  template<class Policies1> interval(interval<T, Policies1> const &r);
  template<class T1, class Policies1> interval(interval<T1, Policies1> const &r);

  interval &operator=(T const &v);
  template<class T1> interval &operator=(T1 const &v);
  interval &operator=(interval<T, Policies> const &r);
  template<class Policies1> interval &operator=(interval<T, Policies1> const &r);
  template<class T1, class Policies1> interval &operator=(interval<T1, Policies1> const &r);

  void assign(T const &l, T const &u);

  T const &lower() const;
  T const &upper() const;

  static interval empty();
  static interval whole();
  static interval hull(T const &x, T const &y);

  interval& operator+= (T const &r);
  interval& operator-= (T const &r);
  interval& operator*= (T const &r);
  interval& operator/= (T const &r);
  interval& operator+= (interval const &r);
  interval& operator-= (interval const &r);
  interval& operator*= (interval const &r);
  interval& operator/= (interval const &r);
};

构造函数创建一个包含其参数的区间。如果有两个参数,则第一个参数假定为左边界,第二个参数假定为右边界。因此,参数需要排序。如果不遵守 !(l <= u) 属性,将使用检查策略创建一个空区间。如果没有给出参数,则创建的区间是单例零。

如果参数的类型与基础数字类型相同,则这些值将直接用于边界。如果类型不同,库将使用舍入策略来转换参数(conv_downconv_up)并创建一个包含区间。当参数是一个具有不同策略的区间时,将检查输入区间以便正确传播其空状态(如果为空)。

赋值运算符的行为类似,只是它们显然只接受一个参数。还有一个 assign 函数可以直接更改区间的边界。如果边界未排序,它的行为类似于双参数构造函数。没有直接接受区间或仅一个数字作为参数的 assign 函数;在这种情况下,只需使用赋值运算符。

区间类型的边界类型和策略定义了区间包含的值集。例如,使用默认策略,区间是实数集的子集。静态函数 emptywhole 分别生成空子集和全集的区间/子集。它们是静态成员函数而不是全局函数,因为它们无法猜测其返回类型。hull 函数同样如此。emptywhole 函数涉及检查策略以获取结果区间的边界。

运算和函数

以下某些函数要求为基础类型定义 minmax。这些是 interval 类的唯一要求(但策略可能有其他要求)。

运算符 + - * / += -= *= /=

基本运算是单元负号和二元运算符 + - * /。单元负号接受一个区间并返回一个区间。二元运算接受两个区间,或一个区间和一个数字,并返回一个区间。如果参数是数字而不是区间,则可以预期结果与将数字首先转换为区间相同。此属性对以下所有函数和运算符均适用。

还有一些赋值运算符 += -= *= /=。无需多言:x op= y 等效于 x = x op y。如果在计算期间抛出异常,则左值不会被修改(但如果在赋值期间由基础类型抛出异常,则它可能已损坏)。

如果分母恰好为零,运算符 //= 将尝试生成一个空区间。如果分母包含零(但不仅仅是零),则结果将是包含除法结果集的最小区间;因此,它的一个边界将是无限的,但它可能不是整个区间。

lower upper median width norm

loweruppermedian 分别计算区间的下限、上限和中位数((lower+upper)/2 四舍五入到最接近的数)。width 计算区间的宽度(upper-lower 向正无穷舍入)。norm 计算区间绝对值的上限;它是一个数学范数(因此得名),类似于实数的绝对值。

min max abs square pow nth_root division_part? multiplicative_inverse

还定义了函数 minmaxabs。请不要将它们与标准库中定义的函数混淆(也就是 a<b?a:ba>b?a:ba<0?-a:a)。这些函数与区间算术的基本属性兼容。例如,max([a,b], [c,d]) = {max(x,y) 使得 x 在 [a,b] 中且 y 在 [c,d] 中}。它们不是在 std 命名空间中定义,而是在 boost 命名空间中定义,以避免与其他定义冲突。

square 函数非常特殊。正如您从其名称中可以预期的那样,它计算其参数的平方。提供此函数的原因是:当 x 包含零时,square(x) 不是 x*x,而只是一个子集。例如,[-2,2]*[-2,2] = [-4,4] 但 [-2,2]² = [0,4];结果是一个较小区间。因此,由于其更好的精度和少量的性能改进,应该使用 square(x) 而不是 x*x

至于 squarepow 提供了一种有效且更准确的方法来计算区间的整数幂。请注意:当幂为 0 且区间不为空时,结果为 1,即使输入区间包含 0。nth_root 计算区间的整数根(nth_root(pow(x,k),k) 包含 xabs(x),具体取决于 k 是奇数还是偶数)。如果整数参数不是正数,则未定义 nth_root 的行为。multiplicative_inverse 计算 1/x

当用户期望除法在必要时返回不相交的区间时,函数 division_part1division_part2 很有用。例如,包含 [2,3] / [-2,1] 的最窄闭集不是 ]-∞,∞[ 而是 ]-∞,-1] 和 [2,∞[ 的并集。当除法的结果可以用一个区间表示时,division_part1 返回此区间并将布尔引用设置为 false。但是,如果结果需要两个区间,则 division_part1 返回负部分并将布尔引用设置为 true;现在需要调用 division_part2 来获取正部分。第二个函数可以将第一个函数返回的布尔值作为最后一个参数。如果没有给出此布尔值,则假定其值为 true,如果除法不产生第二个区间,则该函数的行为未确定。

intersect hull overlap in zero_in subset proper_subset empty singleton equal

intersect 计算两个闭集的交集,hull 计算包含两个参数的最小区间;这些参数可以是数字或区间。如果其中一个参数是无效数字或空区间,则该函数将仅使用另一个参数来计算结果区间(如果检查策略允许)。

没有并集函数,因为如果两个区间不重叠,则它们的并集不是区间。如果它们重叠,则 hull 函数计算并集。

函数 overlap 测试两个区间是否有一些公共子集。in 测试一个数字是否在区间内;zero_in 是一种测试零是否在区间内的变体。subset 测试第一个区间是否是第二个区间的子集;proper_subset 测试它是否是真子集。emptysingleton 测试一个区间是否为空或单例。最后,equal 测试两个区间是否相等。

sqrt log exp sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh fmod

还定义了函数 sqrtlogexpsincostanasinacosatansinhcoshtanhasinhacoshatanh。无需多言;这些函数将传统函数扩展到区间,并遵循区间算术的基本属性。当输入区间严格位于函数域之外时,它们使用检查策略来生成空区间。

函数 fmod(interval x, interval y) 期望 y 的下限严格为正,并返回一个区间 z,使得 0 <= z.lower() < y.upper() 并且 zx-n*y 的超集(其中 n 是整数)。因此,如果两个参数都是正单例,则此函数 fmod(interval, interval) 的行为将类似于传统函数 fmod(double, double)

请注意,fmod 不遵循算术区间的包含属性。例如,fmod([13,17],[7,8]) 的结果应为 [0,8](因为它必须包含 [0,3] 和 [5,8])。但是,当目的是限制区间以计算周期函数时,此答案并不是真正有用。这就是 fmod 将回答 [5,10] 的原因。

add sub mul div

这四个函数接受两个数字并返回操作的封闭区间。它避免在操作之前将数字转换为区间,这可以使用较差的优化器 menghasilkan 代码更好。

常量

一些常量隐藏在 boost::numeric::interval_lib 命名空间中。它们需要由区间类型显式模板化。函数是 pi<I>()pi_half<I>()pi_twice<I>(),它们返回区间类型 I 的对象。它们各自的值为 π、π/2 和 2π。

异常抛出

区间类及其周围定义的所有函数本身从不抛出任何异常。但是,这并不意味着操作永远不会抛出异常。例如,让我们考虑复制构造函数。如前所述,它是编译器生成的默认复制构造函数。因此,如果基类型的复制构造函数不抛出异常,它也不会抛出异常。

相同的情况适用于所有函数:仅当基本类型或两个策略之一抛出异常时才会抛出异常。

区间支持库

区间支持库由一系列类组成,这些类可以结合使用以构建几乎所有常用的区间策略。与简单地属于命名空间 `boost` 的 `interval<T>`(以及此类型中作为隐式第二个模板参数的默认策略)一起使用的基本类和函数相比,这些组件属于命名空间 `boost::numeric::interval_lib`。

我们在这里只给出概要,并将每个部分推迟到单独的网页,因为它只针对高级用户。这允许用示例扩展每个主题,而不会过度扩展本文档的限制。

概要

namespace boost {
namespace numeric {
namespace interval_lib {

/* built-in rounding policy and its specializations */
template <class T>  struct rounded_math;
template <>         struct rounded_math<float>;
template <>         struct rounded_math<double>;
template <>         struct rounded_math<long double>;

/* built-in rounding construction blocks */
template <class T>  struct rounding_control;

template <class T, class Rounding = rounding_control<T> >  struct rounded_arith_exact;
template <class T, class Rounding = rounding_control<T> >  struct rounded_arith_std;
template <class T, class Rounding = rounding_control<T> >  struct rounded_arith_opp;

template <class T, class Rounding>  struct rounded_transc_dummy;
template <class T, class Rounding = rounded_arith_exact<T> >  struct rounded_transc_exact;
template <class T, class Rounding = rounded_arith_std  <T> >  struct rounded_transc_std;
template <class T, class Rounding = rounded_arith_opp  <T> >  struct rounded_transc_opp;

template <class Rounding> struct save_state;
template <class Rounding> struct save_state_nothing;

/* built-in checking policies */
template <class T> struct checking_base;
template <class T, class Checking = checking_base<T>, class Exception = exception_create_empty>   struct checking_no_empty;
template <class T, class Checking = checking_base<T> >                                            struct checking_no_nan;
template <class T, class Checking = checking_base<T>, class Exception = exception_invalid_number> struct checking_catch_nan;
template <class T> struct checking_strict;

/* some metaprogramming to manipulate interval policies */
template <class Rounding, class Checking> struct policies;
template <class OldInterval, class NewRounding> struct change_rounding;
template <class OldInterval, class NewChecking> struct change_checking;
template <class OldInterval> struct unprotect;

/* constants, need to be explicitly templated */
template<class I> I pi();
template<class I> I pi_half();
template<class I> I pi_twice();

/* interval explicit comparison functions:
 * the mode can be cer=certainly or pos=possibly,
 * the function lt=less_than, gt=greater_than, le=less_than_or_equal_to, ge=greater_than_or_equal_to
 *   eq=equal_to, ne= not_equal_to */
template <class T, class Policies>  bool cerlt(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool cerlt(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool cerlt(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool cerle(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool cerle(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool cerle(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool cergt(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool cergt(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool cergt(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool cerge(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool cerge(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool cerge(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool cereq(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool cereq(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool cereq(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool cerne(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool cerne(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool cerne(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool poslt(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool poslt(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool poslt(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool posle(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool posle(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool posle(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool posgt(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool posgt(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool posgt(const T& x, const interval<T, Policies> & y);

template <class T, class Policies>  bool posge(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool posge(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool posge(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool poseq(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool poseq(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool poseq(const T& x, const interval<T, Policies>& y);

template <class T, class Policies>  bool posne(const interval<T, Policies>& x, const interval<T, Policies>& y);
template <class T, class Policies>  bool posne(const interval<T, Policies>& x, const T& y);
template <class T, class Policies>  bool posne(const T& x, const interval<T, Policies>& y);

/* comparison namespaces */
namespace compare {
  namespace certain;
  namespace possible;
  namespace lexicographic;
  namespace set;
  namespace tribool;
} // namespace compare

} // namespace interval_lib
} // namespace numeric
} // namespace boost

区间支持库的每个组件都在其自己的页面中详细介绍。

常见的陷阱和危险

比较

最大的问题之一可能是比较函数和运算符的正确使用。首先,函数和运算符不会试图知道两个区间是否是相同的数学对象。因此,如果使用的比较是“确定”的,那么除非 `x` 是单例区间,否则 `x != x` 始终为真;同样的问题也出现在 `cereq` 和 `cerne` 中。

比较的另一个误导性解释是:你不能总是期望 [a,b] < [c,d] 为 !([a,b] >= [c,d]),因为比较不一定是全序的。相等和小于比较应该被视为两个不同的关系运算符。然而,默认的比较运算符确实遵循此属性,因为只要 [a,b] 和 [c,d] 重叠,它们就会抛出异常。

区间值和引用

这个问题是前一个问题 `x != x` 的必然结果。库中的所有函数只考虑区间的值,而不考虑区间的引用。特别是,你不应该期望(除非 `x` 是单例)以下值相等:`x/x` 和 1,`x*x` 和 `square(x)`,`x-x` 和 0,等等。因此,宽区间的产生主要是因为区间算术无法识别同一变量的不同出现。因此,只要有可能,用户必须重写公式以消除同一变量的多次出现。例如,`square(x)-2*x` 的精度远低于 `square(x-1)-1`。

不受保护的舍入

本节所述,当基本类型是基本浮点类型时,加快计算速度的一个好方法是在算法的热点处取消保护区间。这种方法是安全的,并且确实是对区间计算的一种改进。但请记住,在取消保护块内执行的任何基本浮点运算都可能具有未定义的行为(但仅限于当前线程)。并且不要忘记按照示例中所述创建一个舍入对象。

基本原理

该库的目的是通过使用模板化类 `boost::numeric::interval` 提供一种高效且通用的处理区间算术的方法。我们提供基本原理的主要论点是这个类模板的格式。

提供一个基类型为 double 的区间类会更容易。或者遵循 `std::complex` 并只允许 `float`、`double` 和 `long double` 的特化。我们决定不这样做,以便允许自定义类型上的区间,例如固定精度大浮点数库类型(MPFR 等)、有理数等等。

**策略设计。** 尽管将其设为只有一个模板参数的类模板很诱人,但区间算术的多种用途实际上迫使我们使用策略。这个类的行为可以通过两个策略来确定。这些策略被打包成一个策略类,而不是让 `interval` 具有三个模板参数。这既是为了易于使用(策略类可以默认选择),也是为了可读性。

第一个策略提供定义区间类型函数所需的基本类型的所有数学函数。第二个策略设置处理计算过程中遇到的异常情况的方式。

我们可以预见这些策略的任何组合都适用情况。此外,我们希望使用户能够重用 `interval` 类模板,同时选择自己的行为。有关一些示例,请参阅此页面

**舍入策略。** 该库为原始类型 float 和 double 提供了舍入策略的专门实现。为了使这些实现正确且快速,该库需要大量使用舍入模式。一些处理器被直接处理,并提供了一些机制来加速计算。为了只获得几个计算机周期,这似乎是繁重且危险的优化;但实际上,根据计算机的不同,加速因子很容易超过 2 或 3。此外,这些优化不会对接口产生任何重大影响(使用我们选择的设计,可以通过特化或传递不同的模板参数来添加所有内容)。

**Pred/succ。** 在以前的版本中,提供了两个函数 `pred` 和 `succ`,以及各种推论,如 `widen`。其目的是将区间扩大一个 ulp(尽可能小),例如,以确保包含属性。自从使 interval 成为 T 的模板后,我们就无法为随机参数定义 *ulp*。反过来,舍入策略让我们可以完全消除 ulp 的使用,同时使区间更紧密(如果结果是可表示的单例,则无需扩大区间)。我们决定放弃这些函数。

**`std::less` 的特化。** 由于运算符 `<` 取决于用户在本地选择的比较命名空间,因此无法正确地特化 `std::less`。因此,您必须显式地将此类提供给所有可能需要它的算法和模板(例如,`std::map`)。

**输入/输出。** 区间库不包含 I/O 运算符。打印区间值允许进行大量自定义:有些人可能希望输出边界,另一些人可能希望显示区间的中间值和宽度,等等。示例文件 io.cpp 展示了一些可能性,可以作为用户定义自己的运算符的基础。

**与整数的混合运算。** 当使用和重用模板代码时,通常会有像 `2*x` 这样的运算。但是,该库默认情况下不提供它们,因为从 `int` 到基数类型的转换并不总是正确的(想想从 32 位整数到单精度浮点数的转换)。因此,这些函数已被放在一个单独的头文件中,如果用户想要利用这些混合运算符,则需要显式地包含它们。另一点,由于技术定义方式,没有混合比较运算符。

**区间感知函数。** 该库定义的所有函数显然都知道它们正在操作区间,并且它们会根据一般区间算术原理进行操作。因此,它们的行为可能与通常遇到的非区间感知函数的行为不同。例如,`max` 由规范集合扩展定义,结果不一定是两个参数之一(如果区间不重叠,则结果是两个区间之一)。

此行为与返回对其参数之一的引用的 `std::max` 不同。因此,如果用户希望返回引用,则应使用 `std::max`,因为这正是此函数的功能。请注意,当区间重叠时,`std::max` 将抛出异常。此行为并非早于 C++ 标准描述的行为,因为参数不是“等效的”,并且它允许 `a <= b` 和 `&b == &std::max(a,b)` 之间具有等价性(某些特殊情况可能是实现定义的)。但是,它与 SGI 描述的行为不同,因为它即使在“两者都不大于另一个”的情况下也不会返回第一个参数。

历史和致谢

该库主要受到 Jens Maurer 之前工作的启发。关于他工作的一些讨论在这里转载。Jeremy Siek 和 Maarten Keijzer 为 MSVC 和 Sparc 平台提供了一些舍入控制。

Guillaume Melquiond、Hervé Brönnimann 和 Sylvain Pion 从 Jens 遗留下来的库开始,并添加了策略设计。Guillaume 和 Sylvain 努力编写代码,尤其是将舍入模式移植和主要调整到不同的体系结构。Guillaume 进行了大部分编码工作,而 Sylvain 和 Hervé 提供了一些有用的注释,以便编写此库。Hervé 根据 Guillaume 的出色起点重新组织并编写了文档的章节。

本材料部分基于国家科学基金会在 NSF CAREER Grant CCR-0133599 下支持的工作。本材料中表达的任何意见、调查结果和结论或建议均为作者的意见,并不一定反映国家科学基金会 (NSF) 的观点。


Valid HTML 4.01 Transitional

修订版2006-12-25

版权所有 © 2002 Guillaume Melquiond、Sylvain Pion、Hervé Brönnimann,理工大学
版权所有 © 2003-2006 Guillaume Melquiond,里昂高等师范学校

根据 Boost 软件许可证,版本 1.0 分发。(请参阅随附文件 LICENSE_1_0.txt 或复制于 https://boost.ac.cn/LICENSE_1_0.txt