本页内容 简介 概要 模板类 interval 运算和函数 区间支持库 常见陷阱和危险 基本原理 历史和致谢 |
与此页面相关的其他页面 舍入策略 检查策略 策略操作 比较 基数类型要求 选择您自己的区间类型 测试和示例程序 头文件包含 待办事项列表中的一些项目 |
顾名思义,该库旨在帮助操作数学区间。它由单个头文件 <boost/numeric/interval.hpp> 以及一个主要类型组成,该类型可以用作 interval<T>
。实际上,此区间模板声明为 interval<T,Policies>
,其中 Policies
是一个策略类,用于控制区间类的各种行为;interval<T>
只是为类型 T
选择默认策略。
警告! 并非所有处理器、操作系统和编译器的组合都支持原生浮点格式的保证区间算术。以下是已知在使用 interval<float>
和 interval<double>
进行基本算术运算时能够正常工作的系统列表。
-mfpmath=sse2
),该库就支持使用 GCC、Visual C++ ≥ 7.1、Intel 编译器(Windows 上 ≥ 8)、CodeWarrior(≥ 9)的类 x86 硬件。-mfp-rounding-mode=d -mieee
。以上列表并非详尽无遗。即使系统没有为硬件浮点类型提供保证的计算,区间库仍然可以与用户定义的类型一起使用,并用于进行盒式算术。
区间是一对数字,表示这两个数字之间的所有数字。(区间被认为是闭合的,因此包括边界。)该库的目的是将通常的算术函数扩展到区间。这些区间将写成 [a,b] 来表示 a 和 b 之间的所有数字(包括 a 和 b)。a 和 b 可以是无穷大(但它们不能是相同的无穷大),并且 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 标准定义的浮点类型 float
、double
和 long 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>
之外,此级别的用法还提供算术运算符(+
、-
、*
、/
)、代数和分段代数函数(abs
、square
、sqrt
、pow
)、超越函数和三角函数(exp
、log
、sin
、cos
、tan
、asin
、acos
、atan
、sinh
、cosh
、tanh
、asinh
、acosh
、atanh
)以及标准比较运算符(<
、<=
、>
、>=
、==
、!=
),以及几个特定于区间的函数(min
、max
,它们的含义与 std::min
和 std::max
不同;lower
、upper
、width
、median
、empty
、singleton
、equal
、in
、zero_in
、subset
、proper_subset
、overlap
、intersect
、hull
、bisect
)。
对于某些采用多个 interval<T>
类型参数的函数,会考虑包含至少一个 interval<T>
的参数类型 T
和 interval<T>
的所有组合,以避免将 T
类型的参数转换为 interval<T>
类型的单例。这样做是出于效率原因(参数是单例的事实有时会使某些测试变得不必要)。
该库更高级的用法是手动选择策略 Rounding
和 Checking
,并通过使用 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
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_down
和 conv_up
)并创建一个包含区间。当参数是一个具有不同策略的区间时,将检查输入区间以便正确传播其空状态(如果为空)。
赋值运算符的行为类似,只是它们显然只接受一个参数。还有一个 assign
函数可以直接更改区间的边界。如果边界未排序,它的行为类似于双参数构造函数。没有直接接受区间或仅一个数字作为参数的 assign 函数;在这种情况下,只需使用赋值运算符。
区间类型的边界类型和策略定义了区间包含的值集。例如,使用默认策略,区间是实数集的子集。静态函数 empty
和 whole
分别生成空子集和全集的区间/子集。它们是静态成员函数而不是全局函数,因为它们无法猜测其返回类型。hull
函数同样如此。empty
和 whole
函数涉及检查策略以获取结果区间的边界。
以下某些函数要求为基础类型定义 min
和 max
。这些是 interval
类的唯一要求(但策略可能有其他要求)。
+
-
*
/
+=
-=
*=
/=
基本运算是单元负号和二元运算符 +
-
*
/
。单元负号接受一个区间并返回一个区间。二元运算接受两个区间,或一个区间和一个数字,并返回一个区间。如果参数是数字而不是区间,则可以预期结果与将数字首先转换为区间相同。此属性对以下所有函数和运算符均适用。
还有一些赋值运算符 +=
-=
*=
/=
。无需多言:x op= y
等效于 x = x op y
。如果在计算期间抛出异常,则左值不会被修改(但如果在赋值期间由基础类型抛出异常,则它可能已损坏)。
如果分母恰好为零,运算符 /
和 /=
将尝试生成一个空区间。如果分母包含零(但不仅仅是零),则结果将是包含除法结果集的最小区间;因此,它的一个边界将是无限的,但它可能不是整个区间。
lower
upper
median
width
norm
lower
、upper
、median
分别计算区间的下限、上限和中位数((lower+upper)/2
四舍五入到最接近的数)。width
计算区间的宽度(upper-lower
向正无穷舍入)。norm
计算区间绝对值的上限;它是一个数学范数(因此得名),类似于实数的绝对值。
min
max
abs
square
pow
nth_root
division_part?
multiplicative_inverse
还定义了函数 min
、max
和 abs
。请不要将它们与标准库中定义的函数混淆(也就是 a<b?a:b
、a>b?a:b
、a<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
。
至于 square
,pow
提供了一种有效且更准确的方法来计算区间的整数幂。请注意:当幂为 0 且区间不为空时,结果为 1,即使输入区间包含 0。nth_root
计算区间的整数根(nth_root(pow(x,k),k)
包含 x
或 abs(x)
,具体取决于 k
是奇数还是偶数)。如果整数参数不是正数,则未定义 nth_root
的行为。multiplicative_inverse
计算 1/x
。
当用户期望除法在必要时返回不相交的区间时,函数 division_part1
和 division_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
测试它是否是真子集。empty
和 singleton
测试一个区间是否为空或单例。最后,equal
测试两个区间是否相等。
sqrt
log
exp
sin
cos
tan
asin
acos
atan
sinh
cosh
tanh
asinh
acosh
atanh
fmod
还定义了函数 sqrt
、log
、exp
、sin
、cos
、tan
、asin
、acos
、atan
、sinh
、cosh
、tanh
、asinh
、acosh
、atanh
。无需多言;这些函数将传统函数扩展到区间,并遵循区间算术的基本属性。当输入区间严格位于函数域之外时,它们使用检查策略来生成空区间。
函数 fmod(interval x, interval y)
期望 y
的下限严格为正,并返回一个区间 z
,使得 0 <= z.lower() < y.upper()
并且 z
是 x-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) 的观点。
修订版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)