隐式类型转换
隐式类型转换是一种自动的类型转换,只要混合使用不同的类型,编译器就会完成这种隐式类型转换。这里支持表4-1中定义的标量类型(除void
、double
和half
以外)的隐式转换。完成隐式转换时,并不只是重新解释一个表达式的值,而是将这个值转换为新类型的一个等价的值。
考虑下面的例子:
float f = 3; //implicit conversion to float value 3.0
int i = 5.23f; //implicit conversion to float value 5
在这个例子中,值3
转换为float
值3.0f
,然后赋予f
。值5.23f
转换为一个int
值5,然后赋予i
。在第二个例子中,float
值的小数部分被丢弃,因为整数不支持小数值,这是一个不安全的类型转换例子。
指针类型的隐式转换也遵循C99规范中描述的规则。不允许内置矢量数据类型之间进行隐式转换。例如:
float4 f;
int4 i;
f = i; //illegal implicit conversion between vector data types
常规算术转换
有些需要算术类型(整数或浮点类型)操作数的操作符可能会带来转换,并以类似的方式得到结果类型。我们的目的是为操作数和结果确定一个通用的真实类型。对于指定的操作数,各操作数会转换(不改变类型域)为另外一个类型,其相应的真实类型为通用真实类型。为此,我们认为所有矢量类型比标量有更高的转换等级。除非另外明确说明,否则一般来讲通用真实类型也是结果的相应真实类型,如果操作数相同而且很复杂,那么结果的类型域就是操作数的类型域。这种模式称为常规算术转换( usual arithmetic conversion)。
如果操作数是多个矢量类型,则会出现一个编译时错误。矢量类型之间的隐式转换是不允许的;否则,如果只有一个矢量类型,而且所有其他操作数都是标量类型,那么这些标量类型会转换为矢量元素的类型,再宽化为一个新矢量,其中包含原矢量相同数目的元素,这里会把标量值全面复制到这个新矢量。如果某个标量操作数的等级大于矢量元素的类型,就会产生一个编译时错误。对此,等级顺序定义如下:
1)如果一个浮点类型可以准确地表示另一个浮点类型中的所有数值,那么第一个浮点类型的等级大于第二个浮点类型(对此,使用浮点值编码,而不是设备可用编码的子集)。
2)任何浮点类型的等级都大于整数类型的等级。
3)整数类型的等级大于精度较低的整数类型的等级。
4)无符号整数类型的等级大于有相同精度的、有符号整数类型的等级。
5)bool的等级小于所有其他类型。
6)枚举类型的等级等于兼容的整数类型的等级。
7)对于所有类型T1、T2和T3,如果T1的等级大于T2,而且T2的等级大于T3,则T1的等级大于T3。
否则,如果所有操作数都是标量,则按C99规范中定义应用常规算术转换。下面是矢量以及矢量和标量操作数合法使用算术转换的一些例子:
short a;
int4 b;
int4 c = b + a;
在这个例子中,变量a(类型为short)转换为int4,然后完成矢量加法。
int a;
float4 b;
float4 c = b + a;
在前面的例子中,变量a(类型为int)转换为float4,然后完成矢量加法。
float4 a;
float4 b;
float4 c = b + a;
在这个例子中,不需要完成任何转换,因为a、b和c都是相同的类型。下面是对矢量以及矢量和标量操作数非法使用常规算术转换的几个例子:
int a;
short4 b;
short4 c = b + a; //cannot convert & widen int to short4
double a;
float4 b;
float4 c = b + a; //cannot convert & widen double to float4
int4 a;
float4 b;
float4 c = b + a; //cannot cast between different vector types
显式强制类型转换
表4-1中定义的标量数据类型的标准类型强制类型转换会完成适当的变换(除了void
和half
以外)。在下面的例子中,f
存储0x3F800000
,i
存储0x1
,这就是转换f
中的浮点值1.0f
得到的整数值:
float f = 1.0f;
int i = (int)f;
矢量类型之间的显式强制类型转换是不合法的。下面的例子会生成一个编译错误:
int4 i;
uint4 u = (uint4)i; //compile error
float4 f;
int4 i = (int4)f; //compile error
float4 f;
int8 i = (int8)f; //compile error
完成标量到矢量的转换后,会把标量强制转换为所需要的矢量数据类型。强制类型转换还会完成适当的算术转换。转换为内置整数矢量类型时会采用向零舍入模式。转换为内置浮点矢量类型时,则采用就近舍入模式。将bool
强制转换为整数矢量类型时,如果这个bool
值为true
,则矢量分量设置为-1
(也就是说,设置所有位为1);否则,设置为0
。
下面是显式强制类型转换的一些例子:
float4 f = 1.0f;
float4 va = (float4)f; //va is a float4 vector with elements(f,f,f,f)
uchar u = 0xFF;
float4 vb = (float4)u; //vb is a float4 vector with elements ((float)u,(float)u,(float)u,(float)u)
float f = 2.0f;
int2 vc = (int2)f; //vc is an int2 vector with elements ((int)f,(int)f)
uchar4 vtrue = (uchar4)true; //vtrue is a uchar4 vector with elements(0xFF,0xFF,0xFF,0xFF)
显式转换
不允许在矢量类型之间完成隐式转换和显式强制类型转换。不过,有些情况下我们可能需要将一个矢量类型转换为另一个类型。另外,可能有必要指定完成转换时使用的舍入模式,以及转换的结果是否饱和。这对于标量和矢量数据类型都很有用。
考虑下面的例子:
float x;
int i = (int)x;
在这个例子中,x中的值截断为一个整数值,并存储在i中。也就是说,将浮点值转换为一个整数值时,强制类型转换会完成向零舍入。
有时,我们需要将浮点值舍人为最接近的整数。下面的例子展示了通常如何做到这一点:
float x;
int i = (int)(x + 0.5f);
对于大多数x值,这都能正确工作,除非x
为0.5f -1 ulp
或者x
是一个负数。x
是0.5f -1 ulp
时,(int)(x + 0.5f)
返回1
。也就是说,它会向上舍入而不是向下舍入。x
是一个负数时,(int) (x + 0.5f)
会向下舍入而不是向上舍入。
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
int main(void)
{
float a = 0.5f;
float b = a - nextafterf(a, (float)-INFINITY); // a - 1 ulp
printf("a = %8x, b = %8x\n", *(unsigned int*)&a, *(unsigned int*)&b);
printf(" (int) (a + 0.5f) = %d \n", (int)(a + 0.5f));
printf(" (int) (b + 0.5f) = %d \n", (int)(b + 0.5f));
}
可以增加适当的检查来查看x的值,然后完成正确的转换,这样做可以修正这些问题,不过还有一些硬件能够根据大多数设备上的舍入和饱和设置完成这些转换。从性能的角度来看,这很重要:OpenCL C允许开发人员使用适当的硬件ISA完成这些转换而不是借助于在软件中模拟。正是因为这个原因,OpenCL实现了一些内置函数,可以基于一些选项(选择饱和度和4种舍入模式之一)完成一种类型到另一种类型的转换。
可以使用以下函数完成显式转换:
destType convert_destType><_sat><_roundingMode>(sourceType)
destType convert_destTypen><_sat><_roundingMode>(sourceTypen)
这些函数为标量类型(char、uchar、short、ushort、int、uint、long、ulong、float、double、half)和由这些标量类型衍生的内置矢量类型提供了各种转换。操作数和结果类型必须有相同数目的元素。操作数和结果类型可能是相同的类型,在这种情况下,转换对类型或值没有任何影响。
在下面的例子中,convert_int4
将一个uchar4
矢量u
转换为int4
矢量c
:
uchart4 u;
int4 c = convert_int4(u);
在下一个例子中,convert_int
将一个float
标量f
转换为int
标量i
:
float f;
int i = convert_int(f);
舍入模式修饰符可选,如下所示。
_rte 舍入为最近的偶数
_rtz 向零舍入
_rtp 向正无穷大舍入
_rtn 向负无穷大舍入
未指定修饰符 使用这个目标类型的默认舍入模式:转换为整数时为_rtz,转换为浮点类型时为_rte
可选的饱和度修饰符(_sat
)可以用来指定转换的结果必须饱和至结果类型。转换操作数大于可表示的最大目标值或者小于可表示的最小目标值时,称之为越界。在整数类型之间转换时,对于越界的输入,其结果值将等于对应于相应目标元素的源操作数元素中的最低有效位集合。从一个浮点类型转换为一个整数类型时,其行为由具体实现定义。
转换为整数类型时,最好使用可选的饱和模式完成转换,即在转换函数名后面追加_sat
修饰符。在饱和模式中,超出可表示范围的值会强制为采用目标格式的最近的可表示值(NaN
要转换为0
)。
转换为浮点类型时,要遵循IEEE754舍入规则。_sat
修饰符不能用于转换为浮点格式。
下面是几个使用显式转换函数的例子。
下面的例子显示将一个float4
转换为ush-ort4
,这里指定了就近舍入模式和饱和度。图4-2描述了f
中的值和c
中的转换结果。
float4 f = (float4)(-5.0f, 254.5f, 254.6f, 1.2e9f);
ushort4 c = convert_uchar4_sat_rte(f);
下面的例子描述了将一个有符号值转换为无符号值或者对整数类型完成向下转换时,饱和度修饰符的行为:
short4 s;
//negative values clamped to 0
ushort4 u = convert_ushort4_sat(s);
//values > CHAR_MAX converted to CHAR_MAX
//values < CHAR_MIN converted to CHAR_MIN
char4 c = convert_char4_sat(s);
下面的例子展示了指定饱和度和舍入模式修饰符时,将一个浮点值转换为整数值:
float4 f;
//value implementation-defined for f > INT_MAX, f < INT_MAX, or NaN
int4 i = convert_int4(f);
//values > INT_MAX clamp to INT_MAX
//values < INT_MIN clamp to INT_MIN
//NaN should produce 0
//The _rtz rounding mode is used to produce the integer values
int4 i2 = convert_int4_sat(f);
//similar to convert_int4 except that floating-point values
//are rounded to the nearest integer instead of truncated
int4 i3 = convert_int4_rte(f);
//similar to convert_int4_sat except that floating-point values
//are rounded to the nearest integer instead of truncated
int4 i4 = convert_int4_sat_rte(f);
最后一个转换例子显示了有可选的舍人模式修饰符和无舍入模式修饰符的情况下,将一个整数值转换为浮点值:
int4 i;
//convert ints to floats using the round-to-nearest rounding mode
float4 f = convert_float4(i);
//convert ints to floats; integer values that cannot be
//exactly represented as floats should round up to the next
//representable float
float4 f = convert_float4_rtp(i);
将数据重新解释为另一种类型
在有些情况下,你可能希望屏蔽一个浮点类型的符号位。在C中解决这个问题有很多方法,可以使用指针别名或联合,或者使用memcpy。其中,只有memcpy在C99中是严格正确的。由于OpenCL C不支持memcpy,所以我们需要一种不同的方法来完成这种屏蔽操作。我们需要的一般功能是能够将一个数据类型中的位重新解释为另一种数据类型。在前面的例子中,即希望屏蔽一个浮点类型的符号位,我们希望将这些位重新解释为一个无符号整数类型,然后屏蔽符号位。还有另外一些例子,比如使用一个矢量关系操作符的结果,以及抽取一个浮点类型的指数或尾数位。
as_type
和 as_typen
内置函数允许将一个数据类型的位重新解释为另一个相同大小的数据类型。as_type
用于标量数据类型(除bool
和 void
以外),as_typen
则用于矢量数据类型。只有当实现支持适当的扩展时,才支持double
和half
。
下面的例子描述了如何使用as_type 内置函数屏蔽一个浮点类型的符号位:
float f;
uint u;
u = as_uint(f);
f = as_float(u & ~(1 << 31));
如果操作数和结果类型包含相同数目的元素,那么操作数中的位直接返回,不会修改为新类型;如果操作数和结果类型包含的元素个数不同,则会出现两种情况:
1)操作数是一个包含4个分量的矢量;结果是一个包含3个分量的矢量。在这种情况下,操作数的xyz分量与结果有相同的位。结果的w分量则认为未定义。
2)对于所有其他情况,具体行为由实现定义。
下面给出几个例子展示如何使用as_type
和as_typen
。下一个例子显示了如何将一个int
重新解释为一个float
:
uint u = 0x3f800000;
float f = as_float(u);
变量u
(声明为一个无符号整数)包含值0x3f800000
。这表示单精度浮点值1.0
。变量f现在包含浮点值1.0
。
下面的例子中,将一个float4
重新解释为一个int4
:
float4 f = (float4)(1.0f, 2.0f, 3.0f, 4.0f);
int4 i = as_int4(f);
变量i(类型为int4
)在其xyzw
分量中有以下值:0x3f800000
、0x40000000
、0x40400000
、0x40800000
。
下一个例子展示了如何使用as_typen
对浮点矢量类型执行三元选择操作符(?:
):
//Perform the operation f = f < g ? f:0 for components of a vector
float4 f, g;
int4 is_less = f < g;
//Each component of the is_less vector will be 0 if result of <
//operation is false and will be -1 (i.e.., all bits set) if
//the result of < operation is true
f = as_float4(as_int4(f) & is_less);
//This basically selects f or 0 depending on the values in is_less..
下面的例子展示了操作数和结果中元素个数不同的情况,这里as_type
和as_typen
的行为由具体实现定义:
int i;
short2 j = as_short2(i); //Legal, Result is implementation-defined
int4 i;
short8 j = as_short8(i); //Legal, Result is implementation-defined
float4 f;
float3 g = as_float3(f); //Legal, g.xyz will have same values as f.xyz, g.w is undefined
这个例子展示了将一个4分量的矢量重新解释为3分量矢量:
float4 f;
float3 g = as_float3(f); //Legal, g.xyz will have same values as f.xyz, g.w is undefined
下一个例子显示了非法使用as_type
和as_typen
的情况,这会得到编译错误:
float4 f;
double4 g = as_double4(f);
float3 f;
float4 g = as_float4(f);