Types and Casting¶
Identifiers¶
Identifiers must begin with a letter [AZaz], an underscore or an element from the Unicode character categories Lu/Ll/Lt/Lm/Lo/Nl [con22]. The set of permissible continuation characters consists of all members of the aforementioned character sets with the addition of decimal numerals [09]. Identifiers may not override a reserved identifier.
Variables¶
Variables must be named according to the rules for identifiers (See Identifiers). Variables may be assigned values within a program. Variables representing any classical type can be initialized on declaration. Any classical variable or Boolean that is not explicitly initialized is undefined. Classical types can be mutually cast to one another using the typename. See Casting specifics for more details on casting.
Declaration and initialization must be done one variable at a time for both quantum and classical
types. Comma seperated declaration/initialization (int x, y, z
) is NOT allowed for any type. For
example, to declare a set of qubits one must do
qubit q0;
qubit q1;
qubit q2;
and to declare a set of classical variables
int[32] a;
float[32] b = 5.5;
bit[3] c;
bool my_bool = false;
We use the notation s:m:f
to denote the width and precision of fixed point numbers,
where s
is the number of sign bits, m
is the number of integer bits, and f
is the
number of fractional bits. It is necessary to specify lowlevel
classical representations since OpenQASM operates at the intersection of
gates/analog control and digital feedback and we need to be able to
explicitly transform types to cross these boundaries. Classical types
are scoped to the braces within which they are declared.
Quantum types¶
Qubits¶
There is a quantum bit (qubit
) type that is interpreted as a reference to a
twolevel subsystem of a quantum state. The statement qubit name;
declares a reference to a quantum bit. These qubits are referred
to as “virtual qubits” (in distinction to “physical qubits” on
actual hardware; see below). The statement qubit[size] name;
declares a quantum register with size
qubits.
Sizes must always be compiletime constant positive
integers.
Quantum registers are static arrays of qubits
that cannot be dynamically resized.
The label name[j]
refers to a qubit of this register, where
\(j\in \{0,1,\dots,\mathrm{size}(\mathrm{name})1\}\) is an integer.
Note
To be compliant with the base OpenQASM 3.0 specification, an implementation
is only required to allow this “quantumregister indexing” with a
compiletime constant value (those with const
types). Implementations are permitted to treat indexing into a quantum
register with a value of nonconst
type as an error. Consult your
compiler and hardware documentation for details.
// Valid statements
include "stdgates.inc";
qubit[5] q1;
const uint SIZE = 4;
uint runtime_u = 2;
qubit[SIZE] q2; // Declare a 4qubit register.
x q1[0];
z q2[SIZE  2]; // The index operand is of type `const uint`.
// Validity is implementationdefined.
x q1[runtime_u];
// Indexing with a value with a non`const` type (`uint`, in this case) is
// not guaranteed to be supported.
The keyword qreg
is included
for backwards compatibility and will be removed in the future.
Qubits are initially in an undefined state. A quantum reset
operation is one
way to initialize qubit states.
All qubits are global variables. Qubits cannot be declared within gates or subroutines. This simplifies OpenQASM significantly since there is no need for quantum memory management. However, it also means that users or compiler have to explicitly manage the quantum memory.
Physical Qubits¶
While program qubits can be named, hardware qubits are referenced only
by the syntax $[NUM]
. For an n
qubit system, we have physical qubit
references given by $0
, $1
, …, $n1
. These qubit types are
used in lower parts of the compilation stack when emitting physical
circuits. Physical qubits must not be declared and they are, as all the qubits, global variables.
// Declare a qubit
qubit gamma;
// Declare a qubit with a Unicode name
qubit γ;
// Declare a qubit register with 20 qubits
qubit[20] qubit_array;
// CNOT gate between physical qubits 0 and 1
CX $0, $1;
Classical scalar types¶
Classical bits and registers¶
There is a classical bit type that takes values 0 or 1. Classical
registers are static arrays of bits. The classical registers model part
of the controller state that is exposed within the OpenQASM program. The
statement bit name;
declares a classical bit, and or bit[size] name;
declares a register of
size
bits. The label name[j]
refers to a bit of this register, where \(j\in
\{0,1,\dots,\mathrm{size}(\mathrm{name})1\}\) is an integer.
Bit registers may also be declared as creg name[size]
. This is included for backwards
compatibility and may be removed in the future.
For convenience, classical registers can be assigned a text string containing zeros and ones of the same length as the size of the register. It is interpreted to assign each bit of the register to corresponding value 0 or 1 in the string, where the leastsignificant bit is on the right.
// Declare a register of 20 bits
bit[20] bit_array;
// Declare and assign a register of bits with decimal value of 15
bit[8] name = "00001111";
Integers¶
There are nbit signed and unsigned integers. The statements int[size] name;
and uint[size] name;
declare
signed 1:n1:0 and unsigned 0:n:0 integers of the given size. The sizes
and the surrounding brackets can be omitted (e.g. int name;
) to use
a precision that is specified by the particular target architecture.
Bitlevel operations cannot be used on types without a specified width, and
unspecifiedwidth types are different to all specifiedwidth types for
the purposes of casting.
Because register indices are integers, they
can be cast from classical registers containing measurement outcomes and
may only be known at run time. An nbit classical register containing
bits can also be reinterpreted as an integer, and these types can be
mutually cast to one another using the type name, e.g. int[16](c)
. As noted, this
conversion will be done assuming littleendian bit ordering. The example
below demonstrates how to declare, assign and cast integer types amongst
one another.
// Declare a 32bit unsigned integer
uint[32] my_uint = 10;
// Declare a 16 bit signed integer
int[16] my_int;
my_int = int[16](my_uint);
// Declare a machinesized integer
int my_machine_int;
Floating point numbers¶
IEEE 754 floating point registers may be declared with float[size] name;
, where float[64]
would
indicate a standard doubleprecision float. Note that some hardware
vendors may not support manipulating these values at runtime.
Similar to integers, floatingpoint registers can be declared with an unspecified size. The resulting precision is then set by the particular target architecture, and the unspecifiedwidth type is different to all specifiedwidth types for the purposes of casting.
// Declare a singleprecision 32bit float
float[32] my_float = π;
// Declare a machineprecision float.
float my_machine_float = 2.3;
Void type¶
Subroutines and externs that do not return a value implicitly return void
.
The void
type is unrealizable and uninstantiable, and thus cannot be
attached to an identifier or used as a cast operator. The keyword void
is
reserved for potential future use.
Angles¶
OpenQASM 3 includes a new type to represent classical angles: angle
.
This type is intended to make manipulations of angles more efficient at runtime,
when the hardware executing the program does not have builtin support for
floatingpoint operations. The manipulations on angle
values are designed
to be significantly less expensive when done using integer hardware than the
equivalent software emulation of floatingpoint operations, by using the
equivalence of angles modulo \(2\pi\) to remove the need for large dynamic
range.
In brief, the type angle[size]
is manipulated very similarly to a single
unsigned integer, where the value 1
represents an angle of
\(2\pi/2^{\text{size}}\), and the largest representable value is
this subtracted from \(2\pi\). Addition with other angles, and
multiplication and division by unsigned integers is defined by standard
unsignedinteger arithmetic, with more details found in the section on
classical instructions.
The statement angle[size] name;
statement declares a new angle called
name
with size
bits in its representation. Angles can be assigned
values using the constant π
or pi
, such as:
// Declare a 20bit angle with the value of "π/2"
angle[20] my_angle = π / 2;
// Declare a machinesized angle
angle my_machine_angle;
The bit representation of the type angle[size]
is such that if
angle_as_uint
is the integer whose representation as a uint[size]
has
the same bit pattern, the value of the angle (using exact mathematical
operations on the field of real numbers) would be
This “mathematical” value is the value used in casts from floatingpoint values
(if available), whereas casts to and from bit[size]
types reinterpret the
bits directly. This means that, unless a
is sufficiently small:
float[32] a;
angle[32](bit[32](uint[32](a))) != angle[32](a)
Explicitly, the most significant bit (bit index size  1
) correpsonds to
\(\pi\), and the least significant bit (bit index 0
) corresponds to
\(2^{\text{size} + 1}\pi\). For example, with the mostsignificant bit on
the left in the bitstrings:
angle[4] my_pi = π; // "1000"
angle[6] my_pi_over_two = π/2; // "010000"
angle[8] my_angle = 7 * (π / 8); // "01110000"
Angles outside the interval \([0, 2\pi)\) are represented by their values
modulo \(2\pi\). Up to this modulo operation, the closest angle[size]
representation of an exact mathematical value is different from the true value
by at most \(\epsilon\leq \pi/2^{\text{size}}\).
Complex numbers¶
Complex numbers may be declared as complex[float[size]] name
, where size
is the size of the IEEE754 floatingpoint number used to store the real and
imaginary components. Each component behaves as a float[size]
type. The
designator [size]
can be omitted to use the default hardware float
, and
complex
with no arguments is a synonym for complex[float]
.
Imaginary literals are written as a decimalinteger or floatingpoint literal
followed by the letters im
. There may be zero or more spaces between the
numeric component and the im
component. The type of this token is
complex
(its value has zero real component), and the component type is as
normal given the floatingpoint literal, or the machinesize float
if the
numeric component is an integer.
The real and imaginary components of a complex number can be extracted using the
builtin functions real()
and imag()
respectively. The output types of
these functions is the component type specified in the type declaration. For
example, given a declaration complex[float[64]] c;
the output type of
imag(c)
would be float[64]
. The real()
and imag()
functions
can be used in compiletime constant expressions when called on compiletime
constant values.
complex[float[64]] c;
c = 2.5 + 3.5im;
complex[float] d = 2.0+sin(π/2) + (3.1 * 5.5 im);
float d_real = real(d); // equal to 3.0
Note
Realworld hardware may not support runtime manipulation of complex
values. Consult your hardware’s documentation to determine whether these
language features will be available at run time.
Warning
The OpenQASM 3.0 specification only directly permits complex numbers with
floatingpoint component types. Individually language implementations may
choose to make other component types available, but this version of the
specification prescribes no portable semantics in these cases. It is
possible that a later version of the OpenQASM specification will define
semantics for nonfloat
component types.
Boolean types¶
There is a Boolean type bool name;
that takes values true
or false
. Qubit measurement results
can be converted from a classical bit
type to a Boolean using bool(c)
, where 1 will
be true and 0 will be false.
bit my_bit = 0;
bool my_bool;
// Assign a cast bit to a boolean
my_bool = bool(my_bit);
Compiletime constants¶
A typed declaration of a scalar type may be modified by the const
keyword,
such as const int a = 1;
. This defines a compiletime constant. Values of
type const T
may be used in all locations where a value of type T
is
valid. const
typed values are required when specifying the widths of types
(e.g. in float[SIZE] f;
, SIZE
must have a const
unsigned integer
type). All scalar literals are const
types.
// Valid statements
const uint SIZE = 32; // Declares a compiletime unsigned integer.
qubit[SIZE] q1; // Declares a 32qubit register called `q1`.
int[SIZE] i1; // Declares a signed integer called `i1` with 32 bits.
// Invalid statements
uint runtime_size = 32;
qubit[runtime_size] q2; // Invalid; runtime_size is not a `const` type.
int[runtime_size] i2; // Invalid for the same reason.
Identifiers whose type is const T
must be initialized, and may not be
assigned to in subsequent statements. The type of the result of the
initialization expression for a const
declaration must be const S
, where
S
is a type that is either T
or can be implicitly promoted to T
.
// Valid statements
const uint u1 = 4;
const int[8] i1 = 8;
float[64] runtime_f1 = 2.0;
const uint u2 = u1; // `u1` is of type `const uint`.
const float[32] f2 = u1; // `const uint` is implicitly promoted to `const float[32]`.
// Invalid statements
const int[64] i2 = f2; // `const float[32]` cannot be implicitly promoted to `const int[64]`.
const float[64] f3 = runtime_f1; // `runtime_f1` is not `const`.
Operator expressions, e.g. a + b
(addition), a[b]
(bitlevel indexing)
and a == b
(equality), and certain builtin functions acting only on const
operands will be
evaluated at compile time. The resulting values are of type const T
, where
the type T
is the type of the result when acting on nonconst
operands.
// Valid statements
const uint[8] SIZE = 5;
const uint[16] u1 = 2 * SIZE; // Compiletime value 10.
const float[64] f1 = 5.0 * SIZE; // Compiletime value 25.0.
const bit b1 = u1[1]; // Compiletime value `"1"`.
const bit[SIZE  1] b2 = u1[0:3]; // Compiletime value `"1010"`.
The resultant type of a cast to type T
is const T
if the input value has
a type const S
, where values of type S
can be cast to type T
. If
S
cannot be cast to T
, the expression is invalid. The cast operator
does not contain the keyword const
.
// Valid statements
const float[64] f1 = 2.5;
uint[8] runtime_u = 7;
const int[8] i1 = int[8](f1); // `i1` has compiletime value 2.
const uint u1 = 2 * uint(f1); // `u1` has compiletime value 4.
// Invalid statements
const bit[2] b1 = bit[2](f1); // `float[64]` cannot be cast to `bit[2]`.
const int[16] i2 = int[16](runtime_u); // Casting runtime values is not `const`.
The resultant type of any expression involving a value that is not const
is
not const
. The output type of a call to a subroutine defined by a def
,
or a call to a subroutine linked by an extern
statement is not const
.
In these cases, values of type const T
are converted to type T
(which
has no runtime cost and no effect on the value), then evaluation continues as
usual.
// Valid statements
int[8] runtime_i1 = 4;
def f(int[8] a) > int[8] {
return a;
}
// Invalid statements
const int[8] i2 = 2 * runtime_i1;
// Initialization expression has type `int[8]`, not `const int[8]`.
const int[8] i3 = f(runtime_i1);
// Userdefined function calls do not propagate `const` values.
Builtin constants¶
Six identifiers are automatically defined in the global scope at the beginning of all OpenQASM 3 programs. There are two identifiers for each of the mathematical constants \(\pi\), \(\tau = 2\pi\) and Euler’s number \(e\). Each of these values has one ASCIIonly identifier and one singleUnicodecharacter identifier.
Constant 
ASCII 
Unicode 
Approximate Base 10 

\(\pi\) 
pi 
π 
3.1415926535… 
\(\tau = 2\pi\) 
tau 
τ 
6.283185… 
Euler’s number \(e\) 
euler 
ℇ 
2.7182818284… 
Builtin constant expression functions¶
The following identifiers are compiletime functions that take const
inputs
and have a const
output. The normal implicit casting rules apply to the
inputs of these functions.
Note
These functions may not be available for use on runtime values; consult your compiler and hardware documentation for details.
Function 
Input Range/Type, […] 
Output Range/Type 
Notes 

arccos 


Inverse cosine. 
arcsin 


Inverse sine. 
arctan 


Inverse tangent. 
ceiling 


Round to the nearest representable integer equal or greater in value. 
cos 
( 

Cosine. 
exp 


Exponential \(e^x\). 
floor 


Round to the nearest representable integer equal or lesser in value. 
log 


Logarithm base \(e\). 
mod 


Modulus. The remainder from the integer division of the first argument by the second argument. 
popcount 


Number of set (1) bits. 
pow 


\(\texttt{pow(a, b)} = a^b\). For floatingpoint and complex values, the principal value is returned. 
rotl 


Rotate the bits in the representation
of

rotr 


Rotate the bits in the representation
of 
sin 
( 

Sine. 
sqrt 


Square root. This always returns the principal root. 
tan 
( 

Tangent. 
For each builtin function, the chosen overload is the first one to appear in the list above where all given operands can be implicitly cast to the valid input types. The output type is not considered when choosing an overload. It is an error if there is no valid overload for a given sequence of operands.
// Valid statements.
const float[64] f1 = 2.5;
const int[8] i1 = 4;
const uint[4] u1 = 3;
const bit[8] b1 = "0010_1010";
const complex[float[64]] c1 = 1.0 + 2.0im;
const float[64] f2 = 2.0 * exp(f1);
const float[64] f3 = exp(i1);
// The ``float > float`` overload of ``exp`` is chosen in both of these
// cases; in the first, there is an exact type match, in the second the
// ``int[8]`` input can be implicitly promoted to ``float``.
const int[8] i2 = pow(i1, u1);
// Value 64, expression has type `const int`. The first overload of `pow`
// is chosen, because `i1` can be implicitly promoted to `const int` and
// `u1` to `const uint`.
const float[64] f4 = pow(i1, 2);
// Value 0.0625, expression has type `const float`. The second,
// `(float, float) > float`, overload is chosen, because `2` (type
// `const int`) cannot be implicitly promoted to `const uint`, but both
// input types can be implicitly promoted to `float`. The `complex` overload
// is not attempted, because it has lower priority.
const bit[8] b2 = rotl(b1, 3);
// Value "0101_0001", expression has type `const bit[8]`.
// Invalid statements.
const complex[float[64]] c2 = mod(c1, 2);
// No valid overload is possible; the first given operand has type
// `const complex[float[64]]`, which cannot be implicitly promoted to
// `int` or `float`.
Literals¶
There are five types of literals in OpenQASM 3, integer, float, boolean,
bit string, and timing. These literals have const
types.
Integer literals can be written in decimal without a prefix, or as a hex, octal, or
binary number, as denoted by a leading 0x/0X
, 0o
, or 0b/0B
prefix.
Nonconsecutive underscores _
may be inserted between the first and last
digit of the literal to improve readability for large values.
int i1 = 1; // decimal
int i2 = 0xff; // hex
int i3 = 0xffff_ffff // hex with _ for readability
int i4 = 0XBEEF; // uppercase HEX
int i5 = 0o73; // octal
int i6 = 0b1101; // binary
int i7 = 0B0110_1001; // uppercase B binary with _ for readability
int i8 = 1_000_000 // 1 million with _ for readability
 Float literals contain either
one or more digits followed by a
.
and zero or more digits,a
.
followed by one or more digits.
In addition, scientific notation can be used with a signed or unsigned integer exponent.
float f1 = 1.0;
float f2 = .1; // leading dot
float f3 = 0.; // trailing dot
float f4 = 2e10; // scientific
float f5 = 2e+1; // scientific with positive signed exponent
float f6 = 2.0E1; // uppercase scientific with signed exponent
The two boolean literals are true
and false
.
Bit string literals are denoted by double quotes "
surrounding a number of
zero and one digits, and may include nonconsecutive underscores to improve
readability for large strings.
bit[8] b1 = "00010001";
bit[8] b2 = "0001_0001"; // underscore for readability
Timing literals are float or integer literals with a unit of time.
ns, μs, us, ms, and s
are used for SI time units. dt
is a
backenddependent unit equivalent to one waveform sample.
duration one_second = 1000ms;
duration thousand_cycles = 1000dt;
Arrays¶
Staticallysized arrays of values can be created and initialized, and individual elements can be accessed, using the following general syntax:
array[int[32], 5] myArray = {0, 1, 2, 3, 4};
array[float[32], 3, 2] multiDim = {{1.1, 1.2}, {2.1, 2.2}, {3.1, 3.2}};
int[32] firstElem = myArray[0]; // 0
int[32] lastElem = myArray[4]; // 4
int[32] alsoLastElem = myArray[1]; // 4
float[32] firstLastElem = multiDim[0, 1]; // 1.2
float[32] lastLastElem = multiDim[2, 1]; // 3.2
float[32] alsoLastLastElem = multiDim[1, 1]; // 3.2
myArray[4] = 10; // myArray == {0, 1, 2, 3, 10}
multiDim[0, 0] = 0.0; // multiDim == {{0.0, 1.2}, {2.1, 2.2}, {3.1, 3.2}}
multiDim[1, 1] = 0.0; // multiDim == {{0.0, 1.2}, {2.1, 2.2}, {3.1, 0.0}}
The first argument to the array
declaration is the base type
of the array. The supported classical types include various sizes of bit
,
int
, uint
, float
, complex
, and angle
, as well as
bool
and duration
. Note that stretch
is not a valid array
base type.
Arrays cannot be resized or reshaped. Arrays are statically typed, and cannot implicitly convert to or from any other type.
The size of an array is constant and immutable, and is recorded once, at declaration time.
Array declarations allocate memory of suitable size and alignment to accommodate the storage of its elements.
If the array declaration is directinitialized via initializer list, its elements are initialized to the values provided by the initializer list. Otherwise, the memory allocated is not initialized, and that memory’s content is undefined.
Arrays may be passed as parameters or arguments to functions.
When an array, or slice of an array, is passed as argument to a function, the behavior accords to that described in Arrays in subroutines.
Arrays cannot be declared inside the body of a function or gate. All arrays
must be declared within the global scope of the program.
Indexing of arrays is nbased i.e., negative indices are allowed.
The index 1
means the last element of the array, 2
is the second to
last, and so on, with n
being the first element of an nelement array.
Multidimensional arrays (as in the example above) are allowed, with a maximum
of 7 total dimensions. The subscript operator []
is used for element access,
and for multidimensional arrays subarray accesses can be specified using a
commadelimited list of indices (e.g. myArr[1, 2, 3]
), with the outer
dimension specified first.
For interoperability, the standard
ways of declaring quantum registers and bit registers are equivalent to the
array syntax version (i.e. qubit[5] q1;
is the same as
array[qubit, 5] q1;
).
Assignment to elements of arrays, as in the examples above, acts as expected,
with the lefthand side of the assignment operating as a reference, thereby
updating the values inside the original array. For multidimensional arrays,
the shape and type of the assigned value must match that of the reference.
array[int[8], 3] aa;
array[int[8], 4, 3] bb;
bb[0] = aa; // all of aa is copied to first element of bb
bb[0, 1] = aa[2] // last element of aa is copied to one element of bb
bb[0] = 1 // error  shape mismatch
Arrays may be passed to subroutines and externs. For more details, see Arrays in subroutines.
Aliasing¶
The let
keyword allows declared quantum bits and registers to be referred to by
another name as long as the alias is in scope.
qubit[5] q;
// myreg[0] refers to the qubit q[1]
let myreg = q[1:4];
Note that physical qubits are not declared and so cannot be aliased.
Index sets and slicing¶
Register concatenation and slicing¶
Two or more registers of the same type (i.e. classical or quantum) can
be concatenated to form a register of the same type whose size is the
sum of the sizes of the individual registers. The concatenated register
is a reference to the bits or qubits of the original registers. The
statement a ++ b
denotes the concatenation of registers a
and b
. A register cannot
be concatenated with any part of itself.
Classical and quantum registers can be indexed in a way that selects a subset of (qu)bits, i.e. by an index set. A register so indexed is interpreted as a register of the same type but with a different size. The register slice is a reference to the original register. A register cannot be indexed by an empty index set.
Similarly, classical arrays can be indexed using index sets. See Array concatenation and slicing.
An index set can be specified by a single integer (signed or unsigned), a
commaseparated list of integers contained in braces {a,b,c,…}
, or a range.
Ranges are written as a:b
or
a:c:b
where a
, b
, and c
are integers (signed or unsigned).
The range corresponds to the set \(\{a, a+c, a+2c, \dots, a+mc\}\)
where \(m\) is the largest integer such that \(a+mc\leq b\) if
\(c>0\) and \(a+mc\geq b\) if \(c<0\). If \(a=b\) then
the range corresponds to \(\{a\}\). Otherwise, the range is the
empty set. If \(c\) is not given, it is assumed to be one, and
\(c\) cannot be zero. Note the index sets can be defined by
variables whose values may only be known at run time.
qubit[2] one;
qubit[10] two;
// Aliased register of twelve qubits
let concatenated = one ++ two;
// First qubit in aliased qubit array
let first = concatenated[0];
// Last qubit in aliased qubit array
let last = concatenated[1];
// Qubits zero, three and five
let qubit_selection = two[{0, 3, 5}];
// First seven qubits in aliased qubit array
let sliced = concatenated[0:6];
// Every second qubit
let every_second = concatenated[0:2:12];
// Using negative ranges to take the last 3 elements
let last_three = two[4:1];
// Concatenate two alias in another one
let both = sliced ++ last_three;
Classical value bit slicing¶
A subset of classical values (int, uint, and angle) may be accessed at the bit level using index sets similar to register slicing. The bit slicing operation always returns a bit array of size equal to the size of the index set.
int[32] myInt = 15; // 0xF or 0b1111
bit[1] lastBit = myInt[0]; // 1
bit[1] signBit = myInt[31]; // 0
bit[1] alsoSignBit = myInt[1]; // 0
bit[16] evenBits = myInt[0:2:31]; // 3
bit[16] upperBits = myInt[16:1];
bit[16] upperReversed = myInt[1:16];
myInt[4:7] = "1010"; // myInt == 0xAF
Bitlevel access is still possible with elements of arrays. It is suggested that
multidimensional access be done using the commadelimited version of the
subscript operator to reduce confusion. With this convention nearly all
instances of multiple subscripts [][]
will be bitlevel accesses of array
elements.
array[int[32], 5] intArr = {0, 1, 2, 3, 4};
// Access bit 0 of element 0 of intArr and set it to 1
intArr[0][0] = 1;
// lowest 5 bits of intArr[4] copied to b
bit[5] b = intArr[4][0:4];
Array concatenation and slicing¶
Two or more classical arrays of the same fundamental type can be concatenated to form an array of the same type whose size is the sum of the sizes of the individual arrays. Unlike with qubit registers, this operation copies the contents of the input arrays to form the new (larger) array. This means that arrays can be concatenated with themselves. However, the array concatenation operator is forbidden to be used directly in the argument list of a subroutine or extern call. If a concatenated array is to be passed to a subroutine then it should be explicitly declared and assigned the concatenation.
array[int[8], 2] first = {0, 1};
array[int[8], 3] second = {2, 3, 4};
array[int[8], 5] concat = first ++ second;
array[int[8], 4] selfConcat = first ++ first;
array[int[8], 2] secondSlice = second[1:2]; // {3, 4}
// slicing with assignment
second[1:2] = first[0:1]; // second == {2, 0, 1}
array[int[8], 4] third = {5, 6, 7, 8};
// combined slicing and concatenation
selfConcat[0:3] = first[0:1] ++ third[1:2];
// selfConcat == {0, 1, 6, 7}
subroutine_call(first ++ third) // forbidden
subroutine_call(selfConcat) // allowed
Arrays can be sliced just like quantum registers using a range a:b:c
and can be indexed using an integer but cannot be indexed by a a commaseparated
list of integers contained in braces {a,b,c,…}
. Slicing uses
the subscript operator []
, but produces an array (or reference in the case
of assignment) with the same number of dimensions as the given identifier.
Array slicing is syntactic sugar for concisely expressing for loops over
multidimensional arrays.
For sliced assignments, as with nonsliced assignments, the shapes and types of
the slices must match.
int[8] scalar;
array[int[8], 2] oneD;
array[int[8], 3, 2] twoD; // 3x2
array[int[8], 3, 2] anotherTwoD; // 3x2
array[int[8], 4, 3, 2] threeD; // 4x3x2
array[int[8], 2, 3, 4] anotherThreeD; // 2x3x4
threeD[0, 0, 0] = scalar; // allowed
threeD[0, 0] = oneD; // allowed
threeD[0] = twoD; // allowed
threeD[0] = oneD; // error  shape mismatch
threeD[0, 0] = scalar // error  shape mismatch
threeD = anotherThreeD // error  shape mismatch
twoD[1:2] = anotherTwoD[0:1]; // allowed
twoD[1:2, 0] = anotherTwoD[0:1, 1]; // allowed
Casting specifics¶
The classical types are divided into the ‘standard’ classical types (bool, int, uint, float, and complex) that exist in languages like C, and the ‘special’ classical types (bit, angle, duration, and stretch) that do not. The standard types follow rules that mimic those of C99 for promotion and conversion in mixed expressions and assignments.
If values with two different types are used as the operands of a binary
operation, the lesser of the two types is cast to the greater of the two. All
complex
are greater than all float
, and all complex
and all
float
are greater than all int
or uint
. Within each level of
complex
and float
, types with greater width are greater than types with
lower width. For more information, see the usual arithmetic conversions in C.
The rules for rank of integer conversions mimic those of C99. For more, see integer promotions, and integer conversions.
Standard and special classical types may only mix in expressions with operators defined for those mixed types, otherwise explicit casts must be provided, unless otherwise noted (such as for assigning float values or expressions to angles). Additionally, angle values will be implicitly promoted or converted in the same manner as unsigned integers when mixed with or assigned to angle values with differing precision.
In general, for any cast between standard types that results in loss of precision, if the source value is larger than can be represented in the target type, the exact behavior is implementation specific and must be documented by the vendor.
Allowed casts¶
Casting To 


Casting From 
bool 
int 
uint 
float 
angle 
bit 
duration 
qubit 
bool 
 
Yes 
Yes 
Yes 
No 
Yes 
No 
No 
int 
Yes 
 
Yes 
Yes 
No 
Yes 
No 
No 
uint 
Yes 
Yes 
 
Yes 
No 
Yes 
No 
No 
float 
Yes 
Yes 
Yes 
 
Yes 
No 
No 
No 
angle 
Yes 
No 
No 
No 
 
Yes 
No 
No 
bit 
Yes 
Yes 
Yes 
No 
Yes 
 
No 
No 
duration 
No 
No 
No 
No* 
No 
No 
 
No 
qubit 
No 
No 
No 
No 
No 
No 
No 
 
*Note: duration
values can be converted to float
using the division operator. See Converting duration to other types
Casting from bool¶
bool
values cast from false
to 0.0
and from true
to 1.0
or
an equivalent representation. bool
values can only be cast to bit[1]
(a single bit), so explicit index syntax must be given if the target bit
has more than 1 bit of precision.
Casting from int/uint¶
int[n]
and uint[n]
values cast to the standard types mimicking C99
behavior. Casting to bool
values follows the convention val != 0
.
As noted above, if the value is too large to be represented in the
target type the result is implementationspecific. However,
casting between int[n]
and uint[n]
is expected to preserve the bit
ordering, specifically it should be the case that x == int[n](uint[n](x))
and vice versa. Casting to bit[m]
is only allowed when m==n
. If the target
bit
has more or less precision, then explicit slicing syntax must be given.
As noted, the conversion is done assuming a littleendian 2’s complement
representation.
Casting from float¶
float[n]
values cast to the standard types mimicking C99 behavior (e.g.
discarding the fractional part for integertype targets). As noted above,
if the value is too large to be represented in the
target type the result is implementationspecific.
Casting a float[n]
value to an angle[m]
involves finding the nearest
representable value modulo \(\text{float}_n(2\pi)\), where ties between two
possible representations are resolved by choosing to have zero in the
leastsignificant bit (i.e. round to nearest, ties to even). Casting the
floatingpoint values inf
, inf
and all representations of NaN
to
angle[m]
is not defined.
For example, given the doubleprecision floatingpoint value:
// The closest doubleprecision representation of 2*pi.
const float[64] two_pi = 6.283185307179586
// For double precision, we have
// (two_pi * (127./512.)) / two_pi == (127./512.)
// exactly.
float[64] f = two_pi * (127. / 512.)
the result of the cast angle[8](f)
should have the bitwise representation
"01000000"
(which represents the exact angle
\(2\pi\cdot\frac{64}{256} = \frac\pi2\)), despite "00111111"
(\(2\pi\cdot\frac{63}{256}\)) being equally close, because of the
roundtonearest tiestoeven behaviour.
Casting from angle¶
angle[n]
values cast to bool
using the convention val != 0
. Casting
to bit[m]
values is only allowed when n==m
, otherwise explicit slicing
syntax must be provided. When casting to bit[m]
, the value is a direct
copy of the bit pattern using the same littleendian ordering as described
above.
When casting between angles of differing precisions (n!=m
): if the target
has more significant bits, then the value is padded with mn
least
significant bits of 0
; if the target has fewer significant bits, then
there are two acceptable behaviors that can be supported by compilers:
rounding and truncation. For rounding the value is rounded to the nearest
value, with ties going to the value with the even least significant bit.
Trunction is likely to have more hardware support. This behavior can be
controlled by the use of a #pragma
.
Casting from bit¶
bit[n]
values cast to bool
using the convention val != 0
. Casting to
int[m]
or uint[m]
is done assuming a little endian 2’s complement
representation, and is only allowed when n==m
, otherwise explicit slicing
syntax must be given. Likewise, bit[n]
can only be cast to angle[m]
when n==m
, in which case an exact perbit copy is done using littleendian
bit order. Finally, casting between bits of differing precisions is not
allowed, explicit slicing syntax must be given.
Converting duration to other types¶
Casting from or to duration values is not allowed, however, operations on
durations that produce values of different types is allowed. For example,
dividing a duration by a duration produces a machineprecision float
.
duration one_ns = 1ns;
duration a = 500ns;
float a_in_ns = a / one_ns; // 500.0
duration one_s = 1s;
float a_in_s = a / one_s; // 5e7