Vector Operators
Table 4.8 describes the list of operators that can be used with vector data types or a combination of vector and scalar data types.
Table 4.8. Operators That Can Be Used with Vector Data Types
Operator Category |
Operator Symbols |
Arithmetic operators |
Add (+) Subtract (-) Multiply (*) Divide (/) Remainder (%) |
Relational operators |
Greater than (>) Less than (<) Greater than or equal (>=) Less than or equal (<=) |
Equality operators |
Equal (==) Not equal (!=) |
Bitwise operators |
And (&) Or (|) Exclusive or (^), not (~) |
Logical operators |
And (&&) Or (||) |
Conditional operator |
Ternary selection operator (?:) |
Shift operators |
Right shift (>>) Left shift (<<) |
Unary operators |
Arithmetic (+ or -) Post- and pre-increment (++) Post- and pre-decrement (--) sizeof, not (!) Comma operator (,) Address and indirection operators (&, *) |
Assignment operators |
=, *=, /=, +=, -=, <<=, >>=, &=, ^=, |= |
The behavior of these operators for scalar data types is as described by the C99 specification. The following sections discuss how each operator works with operands that are vector data types or vector and scalar data types.
Arithmetic Operators
The arithmetic operators—add (+), subtract (-), multiply (*), and divide (/)—operate on built-in integer and floating-point scalar and vector data types. The remainder operator (%) operates on built-in integer scalar and vector data types only. The following cases arise:
- The two operands are scalars. In this case, the operation is applied according to C99 rules.
- One operand is a scalar and the other is a vector. The scalar operand may be subject to the usual arithmetic conversion to the element type used by the vector operand and is then widened to a vector that has the same number of elements as the vector operand. The operation is applied component-wise, resulting in the same size vector.
- The two operands are vectors of the same type. In this case, the operation is applied component-wise, resulting in the same size vector.
For integer types, a divide by zero or a division that results in a value that is outside the range will not cause an exception but will result in an unspecified value. Division by zero for floating-point types will result in ±infinity or NaN as prescribed by the IEEE 754 standard.
A few examples will illustrate how the arithmetic operators work when one operand is a scalar and the other a vector, or when both operands are vectors.
The first example in Figure 4.3 shows two vectors being added:
int4 v_iA = (int4)(7, -3, -2, 5); int4 v_iB = (int4)(1, 2, 3, 4); int4 v_iC = v_iA + v_iB;
Figure 4.3 Adding two vectors
The result of the addition stored in vector v_iC is (8, -1, 1, 9).
The next example in Figure 4.4 shows a multiplication operation where operands are a vector and a scalar. In this example, the scalar is just widened to the size of the vector and the components of each vector are multiplied:
float4 vf = (float4)(3.0f, -1.0f, 1.0f, -2.0f); float4 result = vf * 2.5f;
Figure 4.4 Multiplying a vector and a scalar with widening
The result of the multiplication stored in vector result is (7.5f, -2.5f, 2.5f, -5.0f).
The next example in Figure 4.5 shows how we can multiply a vector and a scalar where the scalar is implicitly converted and widened:
float4 vf = (float4)(3.0f, -1.0f, 1.0f, -2.0f); float4 result = vf * 2;
Figure 4.5 Multiplying a vector and a scalar with conversion and widening
The result of the multiplication stored in the vector result is (6.0f, -2.0f, 2.0f, -4.0f).
Relational and Equality Operators
The relational operators—greater than (>), less than (<), greater than or equal (>=), and less than or equal (<=)—and equality operators—equal (==) and not equal (!=)—operate on built-in integer and floating-point scalar and vector data types. The result is an integer scalar or vector type. The following cases arise:
- The two operands are scalars. In this case, the operation is applied according to C99 rules.
- One operand is a scalar and the other is a vector. The scalar operand may be subject to the usual arithmetic conversion to the element type used by the vector operand and is then widened to a vector that has the same number of elements as the vector operand. The operation is applied component-wise, resulting in the same size vector.
- The two operands are vectors of the same type. In this case, the operation is applied component-wise, resulting in the same size vector.
The result is a scalar signed integer of type int if both source operands are scalar and a vector signed integer type of the same size as the vector source operand. The result is of type char n if the source operands are char n or uchar n ; short n if the source operands are short n , short n , or half n ; int n if the source operands are int n , uint n , or float n ; long n if the source operands are long n , ulong n , or double n .
For scalar types, these operators return 0 if the specified relation is false and 1 if the specified relation is true. For vector types, these operators return 0 if the specified relation is false and -1 (i.e., all bits set) if the specified relation is true. The relational operators always return 0 if one or both arguments are not a number (NaN). The equality operator equal (==) returns 0 if one or both arguments are not a number (NaN), and the equality operator not equal (!=) returns 1 (for scalar source operands) or -1 (for vector source operands) if one or both arguments are not a number (NaN).
Bitwise Operators
The bitwise operators—and (&), or (|), exclusive or (^), and not (~)—operate on built-in integer scalar and vector data types. The result is an integer scalar or vector type. The following cases arise:
- The two operands are scalars. In this case, the operation is applied according to C99 rules.
- One operand is a scalar and the other is a vector. The scalar operand may be subject to the usual arithmetic conversion to the element type used by the vector operand and is then widened to a vector that has the same number of elements as the vector operand. The operation is applied component-wise, resulting in the same size vector.
- The two operands are vectors of the same type. In this case, the operation is applied component-wise, resulting in the same size vector.
Logical Operators
The logical operators—and (&&), or (||)—operate on built-in integer scalar and vector data types. The result is an integer scalar or vector type. The following cases arise:
- The two operands are scalars. In this case, the operation is applied according to C99 rules.
- One operand is a scalar and the other is a vector. The scalar operand may be subject to the usual arithmetic conversion to the element type used by the vector operand and is then widened to a vector that has the same number of elements as the vector operand. The operation is applied component-wise, resulting in the same size vector.
- The two operands are vectors of the same type. In this case, the operation is applied component-wise, resulting in the same size vector.
If both source operands are scalar, the logical operator and (&&) will evaluate the right-hand operand only if the left-hand operand compares unequal to 0, and the logical operator or (||) will evaluate the right-hand operand only if the left-hand operand compares equal to 0. If one or both source operands are vector types, both operands are evaluated.
The result is a scalar signed integer of type int if both source operands are scalar and a vector signed integer type of the same size as the vector source operand. The result is of type char n if the source operands are char n or uchar n ; short n if the source operands are short n or ushort n ; int n if the source operands are int n or uint n ; or long n if the source operands are long n or ulong n .
For scalar types, these operators return 0 if the specified relation is false and 1 if the specified relation is true. For vector types, these operators return 0 if the specified relation is false and -1 (i.e., all bits set) if the specified relation is true.
The logical exclusive operator (^^) is reserved for future use.
Conditional Operator
The ternary selection operator (?:) operates on three expressions (expr1 ? expr2 : expr3). This operator evaluates the first expression, expr1, which can be a scalar or vector type except the built-in floating-point types. If the result is a scalar value, the second expression, expr2, is evaluated if the result compares equal to 0; otherwise the third expression, expr3, is evaluated. If the result is a vector value, then (expr1 ? expr2 : expr3) is applied component-wise and is equivalent to calling the built-in function select(expr3, expr2, expr1). The second and third expressions can be any type as long as their types match or if an implicit conversion can be applied to one of the expressions to make their types match, or if one is a vector and the other is a scalar, in which case the usual arithmetic conversion followed by widening is applied to the scalar to match the vector operand type. This resulting matching type is the type of the entire expression.
A few examples will show how the ternary selection operator works with scalar and vector types:
int4 va, vb, vc, vd; int a, b, c, d; float4 vf; vc = d ? va : vb; // vc = va if d is true, = vb if d is false vc = vd ? va : vb; // vc.x = vd.x ? va.x : vb.x // vc.y = vd.y ? va.y : vb.y // vc.z = vd.z ? va.z : vb.z // vc.w = vd.w ? va.w : vb.w vc = vd ? a : vb; // a is widened to an int4 first // vc.x = vd.x ? va.x : vb.x // vc.y = vd.y ? va.y : vb.y // vc.z = vd.z ? va.z : vb.z // vc.w = vd.w ? va.w : vb.w vc = vd ? va : vf; // error – vector types va & vf do not match
Shift Operators
The shift operators—right shift (>>) and left shift (<<)—operate on builtin integer scalar and vector data types. The result is an integer scalar or vector type. The rightmost operand must be a scalar if the first operand is a scalar. For example:
uint a, b, c; uint2 r0, r1; c = a << b; // legal – both operands are scalars r1 = a << r0; // illegal – first operand is a scalar and // therefore second operand (r0) must also be scalar. c = b << r0; // illegal – first operand is a scalar and // therefore second operand (r0) must also be scalar.
The rightmost operand can be a vector or scalar if the first operand is a vector. For vector types, the operators are applied component-wise.
If operands are scalar, the result of E1 << E2 is E1 left-shifted by log2(N) least significant bits in E2. The vacated bits are filled with zeros. If E2 is negative or has a value that is greater than or equal to the width of E1, the C99 specification states that the behavior is undefined. Most implementations typically return 0.
Consider the following example:
char x = 1; char y = -2; x = x << y;
When compiled using a C compiler such as GCC on an Intel x86 processor, (x << y) will return 0. However, with OpenCL C, (x << y) is implemented as (x << (y & 0x7)), which returns 0x40.
For vector types, N is the number of bits that can represent the type of elements in a vector type for E1 used to perform the left shift. For example:
char2 x = (uchar2)(1, 2); char y = -9; x = x << y;
Because components of vector x are an unsigned char, the vector shift operation is performed as ( (1 << (y & 0x7)), (2 << (y & 0x7))).
Similarly, if operands are scalar, the result of E1 >> E2 is E1 right-shifted by log2(N) least significant bits in E2. If E2 is negative or has a value that is greater than or equal to the width of E1, the C99 specification states that the behavior is undefined. For vector types, N is the number of bits that can represent the type of elements in a vector type for E1 used to perform the right shift. The vacated bits are filled with zeros if E1 is an unsigned type or is a signed type but is not a negative value. If E1 is a signed type and a negative value, the vacated bits are filled with ones.
Unary Operators
The arithmetic unary operators (+ and -) operate on built-in scalar and vector types.
The arithmetic post- and pre- increment (++) and decrement (--) operators operate on built-in scalar and vector data types except the built-in scalar and vector floating-point data types. These operators work component-wise on their operands and result in the same type they operated on.
The logical unary operator not (!) operates on built-in scalar and vector data types except the built-in scalar and vector floating-point data types. These operators work component-wise on their operands. The result is a scalar signed integer of type int if both source operands are scalar and a vector signed integer type of the same size as the vector source operand. The result is of type char n if the source operands are char n or uchar n ; short n if the source operands are short n or ushort n ; int n if the source operands are int n or uint n ; or long n if the source operands are long n or ulong n .
For scalar types, these operators return 0 if the specified relation is false and 1 if the specified relation is true. For vector types, these operators return 0 if the specified relation is false and -1 (i.e., all bits set) if the specified relation is true.
The comma operator (,) operates on expressions by returning the type and value of the rightmost expression in a comma-separated list of expressions. All expressions are evaluated, in order, from left to right. For example:
// comma acts as a separator not an operator. int a = 1, b = 2, c = 3, x; // comma acts as an operator x = a += 2, a + b; // a = 3, x = 5 x = (a, b, c); // x = 3
The sizeof operator yields the size (in bytes) of its operand. The result is an integer value. The result is 1 if the operand is of type char or uchar; 2 if the operand is of type short, ushort, or half; 4 if the operand is of type int, uint, or float; and 8 if the operand is of type long, ulong, or double. The result is number of components in vector * size of each scalar component if the operand is a vector type except for 3-component vectors, which return 4 * size of each scalar component. If the operand is an array type, the result is the total number of bytes in the array, and if the operand is a structure or union type, the result is the total number of bytes in such an object, including any internal or trailing padding.
The behavior of applying the sizeof operator to the image2d_t, image3d_t, sampler_t, and event_t types is implementation-defined. For some implementations, sizeof(sampler_t) = 4 and on some implementation this may result in a compile-time error. For portability across OpenCL implementations, it is recommended not to use the sizeof operator for these types.
The unary operator (*) denotes indirection. If the operand points to an object, the result is an l-value designating the object. If the operand has type "pointer to type," the result has type type. If an invalid value has been assigned to the pointer, the behavior of the indirection operator is undefined.
The unary operator (&) returns the address of its operand.
Assignment Operator
Assignments of values to variables names are done with the assignment operator (=), such as
lvalue = expression
The assignment operator stores the value of expression into lvalue. The following cases arise:
- The two operands are scalars. In this case, the operation is applied according to C99 rules.
- One operand is a scalar and the other is a vector. The scalar operand is explicitly converted to the element type used by the vector operand and is then widened to a vector that has the same number of elements as the vector operand. The operation is applied component-wise, resulting in the same size vector.
- The two operands are vectors of the same type. In this case, the operation is applied component-wise, resulting in the same size vector.
The following expressions are equivalent:
lvalue op= expression lvalue = lvalue op expression
The lvalue and expression must satisfy the requirements for both operator op and assignment (=).