Boost C++ Libraries 首页 人员 常见问题解答 更多

PrevUpHomeNext

定义

介绍
类型和值
C++ 算术类型
数值类型
范围和精度
精确、正确舍入和超出范围的表示
标准(数值)转换
子范围转换方向、子类型和超类型

本节提供数值转换库中使用术语的定义。

符号 下划线文本 表示在 C++ 标准中定义的术语。

粗体 表示此处定义但不在标准中的术语。

C++ 对象模型 (§1.7) 所定义,C++ 程序运行的存储或内存是 字节 的连续序列,其中每个字节是位的连续序列。

对象 是存储区 (§1.8) 的一个区域,并具有一个类型 (§3.9)。

类型 是值的离散集合。

类型为 T 的对象具有一个 对象表示,它是在对象中存储的字节序列 (§3.9/4)

类型为 T 的对象具有一个 值表示,它是由确定该类型对象 的位集 (§3.9/4)。对于 POD 类型 (§3.9/10),此位集由对象表示给出,但存储中的并非所有位都需要参与值表示(字符类型除外):例如,一些位可能用于填充或可能存在陷阱位。

space

由对象持有的 类型化值 是由其值表示决定的值。

抽象值(无类型)是表示在类型中的概念信息(即数字 π)。

对象的 固有值 是形成其对象表示的无符号字符序列的二进制值。

space

抽象 值可以在给定类型中 表示

在类型 T表示 抽象值 V 是获取与抽象值 V 对应的类型化值 v

此操作使用 rep() 运算符表示,如:v=rep(V)vV 在类型 T 中的 表示

例如,抽象值 π 可以用类型 double 表示为 double value M_PI,用类型 int 表示为 int value 3

space

相反,类型化值 可以 抽象

抽象 类型为 T 的类型化值 v,就是要获取其在 T 中的表示为 v 的抽象值 V

此操作使用 abt() 运算符表示,如:V=abt(v)

Vv 在类型 T 中的 抽象

抽象只是一个抽象操作(你无法执行它);但它仍然被定义,因为它将用于给出本文档其余部分的定义。

C++ 语言定义了 基本类型 (§3.9.1)。以下基本类型的子集旨在表示 数字

有符号整型 (§3.9.1/2)

{signed char, signed short int, signed int, signed long int} 可用于表示一般整数(负数和正数)。

无符号整型 (§3.9.1/3)

{unsigned char, unsigned short int, unsigned int, unsigned long int} 可用于表示具有模运算的正整数。

浮点类型 (§3.9.1/8)

{float,double,long double} 可用于表示实数。

整型 (§3.9.1/7)

{{signed integers},{unsigned integers}, bool, char and wchar_t}

算术类型 (§3.9.1/8)

{{integer types},{floating types}}

整型要求具有 二进制 值表示。

此外,相同基类型(shortintlong)的有符号/无符号整型要求具有相同的值表示,即

         int i = -3 ; // suppose value representation is: 10011 (sign bit + 4 magnitude bits)
unsigned int u =  i ; // u is required to have the same 10011 as its value representation.

换句话说,整型有符号/无符号 X 使用相同的值表示,但对其的 解释 不同;即它们的 类型化值 可能不同。

另一个结果是,有符号 X 的范围始终是无符号 X 范围的较小子集,如 §3.9.1/3 所要求。

[Note] 注意

始终记住,与有符号类型不同,无符号类型具有模运算;也就是说,它们不会溢出。这意味着

- 在混合有符号/无符号类型时要格外小心

- 仅在需要模运算或非常大的数字时使用无符号类型。不要仅仅因为打算只处理正值就使用无符号类型(你也可以使用有符号类型来做到这一点)。

本节介绍以下定义,旨在将算术类型与行为类似于数字的用户定义类型集成。一些定义故意很宽泛,以便包括各种各样的用户定义数字类型。

在本库中,术语 数字 指的是抽象数值。

如果类型为 数值,则

  • 它是一种算术类型,或者,
  • 它是一种用户定义的类型,它
    • 表示数值抽象值(即数字)。
    • 可以(隐式或显式地)转换为至少一种算术类型。
    • 具有 范围(可能无界)和 精度(可能动态或无限)。
    • 提供 std::numeric_limits 的特化。

如果数值类型表示的抽象值包含负数,则该类型为 有符号

如果数值类型表示的抽象值不包含负数,则该类型为 无符号

如果数值类型具有模运算(不会溢出),则该类型为 模运算

如果数值类型表示的抽象值为整数,则该类型为 整型

如果数值类型表示的抽象值为实数,则该类型为 浮点型

算术值 是算术类型的类型化值

数值 是数值类型的类型化值

这些定义只是通过引入一个称为 数值 的超集来概括算术类型和值的标准概念。所有算术类型和值都是数值类型和值,但反之则不然,因为用户定义的数值类型不是算术类型。

以下示例阐明了算术类型和数值类型(以及值)之间的区别

// A numeric type which is not an arithmetic type (is user-defined)
// and which is intended to represent integer numbers (i.e., an 'integer' numeric type)
class MyInt
{
    MyInt ( long long v ) ;
    long long to_builtin();
} ;
namespace std {
template<> numeric_limits<MyInt> { ... } ;
}

// A 'floating' numeric type (double) which is also an arithmetic type (built-in),
// with a float numeric value.
double pi = M_PI ;

// A 'floating' numeric type with a whole numeric value.
// NOTE: numeric values are typed valued, hence, they are, for instance,
// integer or floating, despite the value itself being whole or including
// a fractional part.
double two = 2.0 ;

// An integer numeric type with an integer numeric value.
MyInt i(1234);

给定一个数字集 N,它的一些元素可以在数值类型 T 中表示。

类型 T 的可表示值集,或 T 的数值集,是一个数值集,其元素是 N 的某个子集的表示。

例如,int 值的区间 [INT_MIN,INT_MAX]int 类型可表示值的集合,即 int 数字集,对应于抽象值的区间 [abt(INT_MIN),abt(INT_MAX)] 中元素的表示。

类似地,double 值的区间 [-DBL_MAX,DBL_MAX]double 数字集,对应于从 abt(-DBL_MAX)abt(DBL_MAX) 的实数子集。

space

next(x) 表示大于 x 的最小数值。

prev(x) 表示小于 x 的最大数值。

v=prev(next(V))v=next(prev(V)) 是将数值类型的值 v 与数字 V 联系起来的恒等式。

如果数值对 xy 满足 x<y,那么当且仅当 next(x)==y 时,它们是 相邻的

相邻数值之间的抽象距离通常称为 最后一位单位,简称 ulp。ulp 是一种数量,其抽象大小相对于它所对应的数值:如果数值集不是均匀分布的,也就是说,如果相邻数值之间的抽象距离在集合中变化 - 就像浮点数类型一样 -,那么数值 x 之后的 1ulp 的大小可能(通常是)与数值 y 之后的 1ulp 的大小不同,其中 x!=y

由于数字本质上是有序的,因此 T 类型的 数字集 是数值(T 类型)的顺序序列,形式为

REP(T)={l,next(l),next(next(l)),...,prev(prev(h)),prev(h),h}

其中 lh 分别是 T 类型的最小值和最大值,称为 T 类型的边界值。

space

数字集是离散的。它有一个 大小,即集合中数值的个数,一个 宽度,即最高和最低边界值之间的抽象差值:[abt(h)-abt(l)],以及一个 密度,即其大小和宽度之间的关系:density=size/width

整数类型具有密度 1,这意味着在 abt(l)abt(h) 之间没有不可表示的整数(即没有间隙)。另一方面,浮点类型具有远小于 1 的密度,这意味着在相邻的浮点值之间存在未表示的实数(即存在间隙)。

space

抽象值 的区间 [abt(l),abt(h)]T 类型的范围,记为 R(T)

范围是一组抽象值,而不是一组数值。在其他文档中,例如 C++ 标准中,词语 range 有时用作 numeric set 的同义词,即从 lh 的数值的有序序列。但是,在本文件中,范围是一个抽象的区间,它包含了数字集。

例如,序列 [-DBL_MAX,DBL_MAX]double 类型的数字集,而实数区间 [abt(-DBL_MAX),abt(DBL_MAX)] 是它的范围。

注意,例如,浮点数类型的范围是 连续的,与它的数字集不同。

选择此定义是因为

  • (a) 离散的数值集已由数字集给出。
  • (b) 抽象区间更容易比较和重叠,因为只需要考虑边界值。

此定义允许对 subranged 进行简洁的定义,如上一节所述。

如定义的那样,数字集的宽度与范围的宽度完全相同。

space

类型的 精度 由数字集的宽度或密度给出。

对于密度为 1 的整数类型,精度在概念上等效于范围,并由值表示中使用的位数决定:位数越多,数字集的大小越大,范围越广,精度越高。

对于密度 <<1 的浮点类型,精度不是由范围的宽度决定的,而是由密度决定的。在典型的实现中,范围由指数中使用的位数决定,精度由尾数中使用的位数决定(给出可以精确表示的最大有效位数)。指数位数越多,范围越广,而尾数位数越多,精度越高。

给定一个抽象值 V 和一个类型 T 以及它对应的范围 [abt(l),abt(h)]

如果 V < abt(l)V > abt(h),则 V 在类型 T不可表示(不能表示),或者等效地,它在类型 T 中的表示 超出范围,或者 溢出

  • 如果 V < abt(l),则 溢出为负
  • 如果 V > abt(h),则 溢出为正

如果 V >= abt(l)V <= abt(h),则 V 在类型 T可表示(可以表示),或者等效地,它在类型 T 中的表示 在范围内,或者 不会溢出

注意,数值类型,例如 C++ 无符号类型,可以定义任何 V 不会溢出,方法是始终表示的不是 V 本身,而是抽象值 U = [ V % (abt(h)+1) ],它始终在范围内。

给定一个在类型 T 中表示为 v 的抽象值 V,表示的 舍入误差 是抽象差值:(abt(v)-V)

注意,表示是一个 操作,因此,舍入误差对应于表示操作,而不是数值本身(即数值本身没有任何误差)

  • 如果舍入为 0,则表示是 精确的V 在类型 T 中可以精确表示。
  • 如果舍入不为 0,则表示是 不精确的V 在类型 T 中可以不精确表示。

如果类型为 T 的表示 v -无论它是精确的还是不精确的- 是该类型中 V 的相邻值之一,也就是说,如果 v==prevv==next,则表示是忠实舍入的。如果 prevnext 之间的选择与给定的 舍入方向 相匹配,则它是 正确舍入 的。

所有精确表示都是正确舍入的,但并非所有不精确表示都是。特别是,C++ 要求数值转换(在下面描述)和算术运算的结果(本文件未涵盖)为正确舍入,但批处理操作会传播舍入误差,因此最终结果通常是错误舍入的,也就是说,计算结果的数值 r 既不是抽象值的相邻值之一,而抽象值 R 是理论结果。

因为正确舍入的表示总是被表示的抽象值的相邻值之一,所以舍入误差保证最多为 1ulp。

以下示例总结了给定的定义。考虑

  • 一个数值类型 Int 表示整数,具有 数值集: {-2,-1,0,1,2}范围: [-2,2]
  • 一个数值类型 Cardinal 表示整数,具有 数值集: {0,1,2,3,4,5,6,7,8,9}范围: [0,9](这里没有模运算)
  • 一个数值类型 Real 表示实数,具有 数值集: {-2.0,-1.5,-1.0,-0.5,-0.0,+0.0,+0.5,+1.0,+1.5,+2.0}范围: [-2.0,+2.0]
  • 一个数值类型 Whole 表示实数,具有 数值集: {-2.0,-1.0,0.0,+1.0,+2.0}范围: [-2.0,+2.0]

首先,注意类型 RealWhole 都表示实数,具有相同的范围,但精度不同。

  • 整数 1(一个抽象值)可以在这些类型中的任何一个中被精确地表示。
  • 整数 -1 可以被精确地表示在 IntRealWhole 中,但不能表示在 Cardinal 中,会导致负溢出。
  • 实数 1.5 可以被精确地表示在 Real 中,并且可以在其他类型中被不精确地表示。
  • 如果 1.5 在任何类型(除了 Real)中被表示为 12,则表示是正确舍入的。
  • 如果 0.5 在类型 Real 中被表示为 +1.5,则它是错误舍入的。
  • (-2.0,-1.5) 是任何处于区间 [-2.0,-1.5] 中的实数的 Real 相邻值,但对于 x < -2.0,也不存在 Real 相邻值,对于 x > +2.0 也不存在。

C++ 语言定义了 标准转换 (§4),其中一些是在算术类型之间进行转换。

这些是 整数提升 (§4.5)、整数转换 (§4.7)、浮点提升 (§4.6)、浮点转换 (§4.8) 和 浮点整数转换 (§4.9)。

在下文中,整数提升和浮点提升被称为 算术提升,这些提升以及整数、浮点和浮点整数转换被称为 算术转换(即提升是转换)。

提升,无论是整数还是浮点,都是 值保持 的,这意味着类型值在转换后不会改变。

在下文中,考虑源类型为 S 的源类型值为 s,源抽象值为 N=abt(s),目标类型为 T;以及在可能的情况下,目标类型值为 t 的类型为 T

整数到整数的转换总是定义的

  • 如果 T 是无符号的,则实际表示的抽象值不是 N,而是 M=[ N % ( abt(h) + 1 ) ],其中 h 是类型 T 的最高无符号类型值。
  • 如果 T 是有符号的并且 N 不能直接表示,则结果 t实现定义 的,这意味着 C++ 实现需要生成一个值 t,即使它与 s 完全无关。

浮点到浮点的转换仅在 N 可表示时定义;如果不可表示,则转换具有 未定义行为

  • 如果 N 可以被精确地表示,则 t 必须是精确表示。
  • 如果 N 可以被不精确地表示,则 t 必须是两个相邻值之一,并且舍入方向由实现定义;也就是说,转换必须是正确舍入的。

浮点到整数的转换不表示 N,而是表示 M=trunc(N),其中 trunc() 是截断:即删除小数部分,如果有的话。

  • 如果 M 不能在 T 中表示,则转换具有 未定义行为(除非 Tbool,参见 §4.12)。

整数到浮点的转换总是定义的。

  • 如果 N 可以被精确地表示,则 t 必须是精确表示。
  • 如果 N 可以被不精确地表示,则 t 必须是两个相邻值之一,并且舍入方向由实现定义;也就是说,转换必须是正确舍入的。

给定一个源类型 S 和一个目标类型 T,有一个 转换方向,记为:S->T

对于任何两个范围,可以定义以下 范围关系:一个范围 X 可以 完全包含 在一个范围 Y 中,在这种情况下,就说 XY 包含。

形式化: R(S)R(T) 包含,当且仅当 (R(S) intersection R(T) ) == R(S)

如果源类型范围 R(S) 未包含在目标类型范围 R(T) 中;也就是说,如果 (R(S) & R(T) ) != R(S),则称转换方向为 子范围,这意味着 R(S) 未完全包含在 R(T) 中,因此源范围的一部分落在目标范围之外。换句话说,如果转换方向 S->T 是子范围的,则 S 中存在一些值无法在 T 中表示,因为它们超出了范围。请注意,对于 S->T,子范围形容词适用于 T

示例

假设以下所有表示实数的数字类型

  • X,数字集为 {-2.0,-1.0,0.0,+1.0,+2.0},范围为 [-2.0,+2.0]
  • Y,数字集为 {-2.0,-1.5,-1.0,-0.5,0.0,+0.5,+1.0,+1.5,+2.0},范围为 [-2.0,+2.0]
  • Z,数字集为 {-1.0,0.0,+1.0},范围为 [-1.0,+1.0]

对于

(a) X->Y

R(X) & R(Y) == R(X),则 X->Y 不是子范围的。因此,所有类型为 X 的值都可以在类型 Y 中表示。

(b) Y->X

R(Y) & R(X) == R(Y),则 Y->X 不是子范围的。因此,所有类型为 Y 的值都可以在类型 X 中表示,但在这种情况下,一些值是 不精确的 表示的(所有一半的值)。(注意:允许这种情况是因为范围是抽象值的间隔,而不是类型化值的间隔)

(b) X->Z

R(X) & R(Z) != R(X),则 X->Z 是子范围的。因此,一些类型为 X 的值无法在类型 Z 中表示,它们超出了范围 (-2.0 and +2.0)

有可能 R(S) 未包含在 R(T) 中,而 R(T) 也不包含在 R(S) 中;例如,UNSIG=[0,255] 未包含在 SIG=[-128,127] 中;SIG 也不包含在 UNSIG 中。这意味着转换方向可能在两种方向上都是子范围的。当涉及到有符号/无符号类型的混合时,就会出现这种情况,这表明在两种方向上都存在可能超出范围的值。

给定转换方向 S->T 的范围关系(子范围或不是子范围),可以将 ST 分别归类为 超类型子类型:如果转换是子范围的,这意味着 T 无法表示所有可能的类型为 S 的值,则 S 是超类型,T 是子类型;否则,T 是超类型,S 是子类型。

例如

R(float)=[-FLT_MAX,FLT_MAX]R(double)=[-DBL_MAX,DBL_MAX]

如果 FLT_MAX < DBL_MAX

  • double->float 是子范围的,并且 supertype=doublesubtype=float
  • float->double 不是子范围的,并且 supertype=doublesubtype=float

请注意,虽然 double->float 是子范围的,float->double 不是,这使得这两个方向具有相同的超类型和子类型。

现在考虑

R(int)=[INT_MIN,INT_MAX]R(unsigned int)=[0,UINT_MAX]

C++ 实现需要满足 UINT_MAX > INT_MAX (§3.9/3),因此

  • 'int->unsigned' 是子范围的(负值超出了范围),并且 supertype=intsubtype=unsigned
  • 'unsigned->int' 也是 子范围的(高正值超出了范围),并且 supertype=unsignedsubtype=int

在这种情况下,转换在两个方向上都是子范围的,并且超类型和子类型对不是不变的(在方向反转下)。这表明两种类型都不能表示彼此的所有值。

S->TT->S 的超类型相同时,它实际上表明了一种能够表示子类型所有值的类型。因此,如果转换 X->Y 不是子范围的,而相反方向 (Y->X) 是,这样超类型始终为 Y,则称方向 X->Y正确舍入值保留,意味着所有此类转换都保证产生范围内的结果,并且正确舍入(即使不精确)。例如,所有整数到浮点数的转换都是正确舍入值保留的。


PrevUpHomeNext