隐式类型转换
隐式类型转换是一种自动的类型转换,只要混合使用不同的类型,编译器就会完成这种隐式类型转换。这里支持表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);
舍入模式修饰符可选,如下所示。