本页目录 简介 概要 模板类 interval 操作和函数 区间支持库 常见陷阱和危险 理论基础 历史和致谢 |
与本页相关的其他页面 舍入策略 检查策略 策略操作 比较 基数类型要求 选择您自己的区间类型 测试和示例程序 头文件包含 待办事项列表上的一些项目 |
顾名思义,此库旨在帮助操作数学区间。它由单个头文件 <boost/numeric/interval.hpp> 和主要可用作 interval<T>
的类型组成。实际上,此区间模板声明为 interval<T,Policies>
,其中 Policies
是一个策略类,用于控制区间类的各种行为;interval<T>
只是恰好为类型 T
选择默认策略。
警告! 并非所有处理器、操作系统和编译器的组合都支持本机浮点格式的保证区间算术。以下列表是已知在使用基本算术运算符的 interval<float>
和 interval<double>
时可以正常工作的系统。
-mfpmath=sse2
)。-mfp-rounding-mode=d -mieee
。之前的列表并非详尽无遗。即使系统不为硬件浮点类型提供保证的计算,区间库仍然可以与用户定义的类型一起使用,并用于进行盒算术。
区间是一对数字,表示这两个数字之间的所有数字。(区间被认为是闭区间,因此包含边界。)此库的目的是将常用的算术函数扩展到区间。这些区间将写为 [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>
类型参数的函数,为了避免将 T
类型的参数转换为 interval<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)
具有更好的精度和较小的性能提升,因此应使用 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
这四个函数采用两个数字并返回运算的封闭区间。它避免了在运算之前将数字转换为区间,对于优化器较差的代码,它可以产生更好的代码。
一些常量隐藏在 boost::numeric::interval_lib
命名空间中。它们需要通过区间类型显式模板化。这些函数是 pi<I>()
、pi_half<I>()
和 pi_twice<I>()
,它们返回区间类型 I
的对象。它们各自的值是 π、π/2 和 2π。
区间类和围绕此类定义的所有函数本身都不会抛出任何异常。但是,这并不意味着操作永远不会抛出异常。例如,让我们考虑复制构造函数。如前所述,它是编译器生成的默认复制构造函数。因此,如果基类型的复制构造函数不抛出异常,它将不会抛出异常。
相同的情况适用于所有函数:只有当基类型或两个策略之一抛出异常时,才会抛出异常。
区间支持库由可以组合和组合以构建几乎各种常用区间策略的类集合组成。与与 interval<T>
结合使用的基本类和函数(以及默认策略作为此类型中的隐式第二个模板参数)相比,它们仅属于命名空间 boost
,这些组件属于命名空间 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
始终为 true,除非 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 的类 interval 会更容易。或者遵循 std::complex
并仅允许 float
、double
和 long double
的特化。我们决定不这样做,以便允许自定义类型的区间,例如,固定精度 bigfloat 库类型(MPFR 等)、有理数等。
策略设计。 尽管将其设为只有一个模板参数的类模板很诱人,但是区间算术的用途多样性实际上迫使我们使用策略。可以通过两个策略来固定此类的行为。这些策略被打包到一个策略类中,而不是使 interval
具有三个模板参数。这既是为了易于使用(可以默认选择策略类),又是为了提高可读性。
第一个策略提供了基类型上定义区间类型函数所需的所有数学函数。第二个策略设置了处理计算期间遇到的异常情况的方式。
我们可以预见任何这些策略组合都适用的情况。此外,我们希望使用户能够重用 interval
类模板,同时选择自己的行为。有关一些示例,请参见此页面。
舍入策略。 该库为原始类型 float 和 double 提供了舍入策略的专门实现。为了使这些实现正确且快速,该库需要大量使用舍入模式。某些处理器直接处理,并提供了一些机制来加速计算。对于仅获得少量计算机周期的增益而言,这似乎是繁重且危险的优化;但实际上,加速因子很容易超过 2 或 3,具体取决于计算机。此外,这些优化不会以任何主要方式影响接口(使用我们选择的设计,可以通过特化或传递不同的模板参数来添加所有内容)。
Pred/succ。 在以前的版本中,提供了两个函数 pred
和 succ
,以及各种必然结果,例如 widen
。目的是将区间扩大一个 ulp(尽可能小),例如,以确保包含性质。由于使区间成为 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 资助 CCR-0133599 项目支持的工作。 本材料中表达的任何观点、发现、结论或建议均为作者的观点,不一定反映美国国家科学基金会 (NSF) 的观点。
修订2006-12-25
版权所有 © 2002 Guillaume Melquiond, Sylvain Pion, Hervé Brönnimann, Polytechnic University
版权所有 © 2003-2006 Guillaume Melquiond, ENS Lyon
根据 Boost 软件许可协议 1.0 版分发。(请参阅随附文件 LICENSE_1_0.txt 或在 https://boost.ac.cn/LICENSE_1_0.txt 复制)