QuantumFloat#
- class QuantumFloat(msize: int | Array, exponent: int | Array = 0, qs: Any = None, name: str | None = None, signed: bool = False)[source]#
This subclass of QuantumVariable represents signed or unsigned floats to 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 number of mantissa qubits and the -1 indicates the exponent.
Note
msizeandexponentalso accept ajax.Arrayunder Jasp tracing, but only in the sense of a 0-d, traced scalar (e.g. a value computed from a measurement inside@jaspify/make_jaspr). Never an actual multi-element array.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: 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 cannot be stored in a finite number of qubits, forcing us to approximate. To get a better approximation we can use the
q_divandqf_inversionfunctions 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 in-place 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 in-place 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_shiftmethod 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 corresponds to
comparison_qbl_0and the second one iscomparison_qbl_1.
Methods#
As an inheritor, addtionally to the methods listed here, QuantumFloats support all methods of QuantumVariable.
|
Performs an internal bit shift. |
|
Performs a bit shift in the quantum device. |
Returns the qubit with significance \(k\). |
|
Returns the sign qubit. |
|
Turns an unsigned QuantumFloat into its signed version. |
|
|
Receives a regular float and returns the float that is closest to the input but can still be encoded. |
|
Returns the semi-boolean polynomial of this QuantumFloat where m specifies the image extension parameter. |
|
Retrieves the expectation value of self. |