QuantumFloat#

class QuantumFloat(msize, exponent=0, qs=None, name=None, signed=False)[source]#

This subclass of QuantumVariable can represent floating point numbers (signed and unsigned) up to an arbitrary precision.

The technical details of the employed arithmetic can be found in this article.

To create a QuantumFloat we call the constructor:

>>> from qrisp import QuantumFloat
>>> a = QuantumFloat(3, -1, signed = False)

Here, the 3 indicates the amount of mantissa qubits and the -1 indicates the exponent.

For unsigned QuantumFloats, the decoder function is given by

\[f_{k}(i) = i2^{k}\]

Where \(k\) is the exponent.

We can check which values can be represented:

>>> for i in range(2**a.size): print(a.decoder(i))
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5

We see \(2^3 = 8\) values, because we have 3 mantissa qubits. The exponent is -1, implying the precision is \(0.5 = 2^{-1}\).

For signed QuantumFloats, the decoder function is

\[\begin{split}f_{k}^{n}(i) = \begin{cases} i2^{k} & \text{if } i < 2^n \\ (i - 2^{n+1})2^k & \text{else} \end{cases}\end{split}\]

Where \(k\) is again, the exponent and \(n\) is the mantissa size.

Another example:

>>> b = QuantumFloat(2, -2, signed = True)
>>> for i in range(2**b.size): print(b.decoder(i))
0.0
0.25
0.5
0.75
-1.0
-0.75
-0.5
-0.25

Here, we have \(2^2 = 4\) values and their signed equivalents. Their precision is \(0.25 = 2^{-2}\).

Arithmetic

Many operations known from classical arithmetic work for QuantumFloats in infix notation.

Addition:

>>> a[:] = 1.5
>>> b[:] = 0.25
>>> c = a + b
>>> print(c)
{1.75: 1.0}

Subtraction:

>>> d = a - c
>>> print(d)
{-0.25: 1.0}

Multiplication:

>>> e = d * b
>>> print(e)
{-0.0625: 1.0}

And even division:

>>> a = QuantumFloat(3)
>>> b = QuantumFloat(3)
>>> a[:] = 7
>>> b[:] = 2
>>> c = a/b
>>> print(c)
{3.5: 1.0}

Floor division:

>>> d = a//b
>>> print(d)
{3.0: 1.0}

Inversion:

>>> a = QuantumFloat(3, -1)
>>> a[:] = 3.5
>>> b= a**-1
>>> print(b)
{0.25: 1.0}

Note that the latter is only an approximate result. This is because in many cases, the results of division can not be stored in a finite amount of qubits, forcing us to approximate. To get a better approximation we can use the q_div and qf_inversion functions and specify the precision:

>>> from qrisp import q_div, qf_inversion
>>> a = QuantumFloat(3)
>>> a[:] = 1
>>> b = QuantumFloat(3)
>>> b[:] = 7
>>> c = q_div(a, b, prec = 6)
>>> print(c)
{0.140625: 1.0}

Comparing with the classical result (0.1428571428):

>>> 1/7 - 0.140625
0.002232142857142849

We see that the result is inside the expected precision of \(2^{-6} = 0.015625\).

In-place Operations

Further supported operations are inplace addition, subtraction (with both classical and quantum values):

>>> a = QuantumFloat(4, signed = True)
>>> a[:] = 4
>>> b = QuantumFloat(4)
>>> b[:] = 3
>>> a += b
>>> print(a)
{7: 1.0}
>>> a -= 2
>>> print(a)
{5: 1.0}

Warning

Additions that would result in overflow, raise no errors. Instead, the additions are performed modular.

>>> c = QuantumFloat(3)
>>> c += 9
>>> print(c)
{1: 1.0}

For inplace multiplications, only classical values are allowed:

>>> a *= -3
>>> print(a)
{-15: 1.0}

Note

In-place multiplications can change the mantissa size to prevent overflow errors. If you want to prevent this behavior, look into inpl_mult.

>>> a.size
7

Bitshifts

Bitshifts can be executed for free (i.e. not requiring any quantum gates). We can either use the exp_shift method or use the infix operators. Note that the bitshifts work in-place.

>>> a.exp_shift(3)
>>> print(a)
{-120: 1.0}
>>> a >> 5
>>> print(a)
{-3.75: 1.0}

Comparisons

QuantumFloats can be compared to Python floats using the established operators. The return values are QuantumBools:

>>> from qrisp import h
>>> a = QuantumFloat(4)
>>> h(a[2])
>>> print(a)
{0: 0.5, 4: 0.5}
>>> comparison_qbl_0 = (a < 4 )
>>> print(comparison_qbl_0)
{False: 0.5, True: 0.5}

Comparison to other QuantumFloats also works:

>>> b = QuantumFloat(3)
>>> b[:] = 4
>>> comparison_qbl_1 = (a == b)
>>> comparison_qbl_1.qs.statevector()
sqrt(2)*(|0>*|True>*|4>*|False> + |4>*|False>*|4>*|True>)/2

The first tensor factor containing a boolean value is corresponding to comparison_qbl_0 and the second one is comparison_qbl_1.

Methods#

As an inheritor, addtionally to the methods listed here, QuantumFloats support all methods of QuantumVariable.

QuantumFloat.exp_shift(shift)

Performs an internal bit shift.

QuantumFloat.quantum_bit_shift(shift_amount)

Performs a bit shift in the quantum device.

QuantumFloat.significant(k)

Returns the qubit with significance \(k\).

QuantumFloat.sign()

Returns the sign qubit.

QuantumFloat.add_sign()

Turns an unsigned QuantumFloat into its signed version.

QuantumFloat.truncate(x)

Receives a regular float and returns the float that is closest to the input but can still be encoded.

QuantumFloat.sb_poly([m])

Returns the semi-boolean polynomial of this QuantumFloat where m specifies the image extension parameter.

QuantumFloat.get_ev(**mes_kwargs)

Retrieves the expectation value of self.