Repository: Qiskit/qiskit

Issue:
Compiling QFT circuit on 1k qubits fails
### Environment

- **Qiskit Terra version**: latest
- **Python version**: 
- **Operating system**: 


### What is happening?

There is an overflow when converting a Python int to a numerical float

```bash

~/mambaforge/envs/qiskit/lib/python3.8/site-packages/qiskit/circuit/library/basis_change/qft.py in _build(self)
    259             num_entanglements = max(0, j - max(0, self.approximation_degree - (num_qubits - j - 1)))
    260             for k in reversed(range(j - num_entanglements, j)):
--> 261                 lam = np.pi / (2 ** (j - k))
    262                 circuit.cp(lam, j, k)
    263 

OverflowError: int too large to convert to float
```

### How can we reproduce the issue?

```python
from qiskit import transpile
from qiskit.transpiler.coupling import CouplingMap
from qiskit.circuit.library import EfficientSU2, QFT

cmap = CouplingMap.from_heavy_hex(21)
qc = QFT(cmap.size())

transpile(qc, coupling_map=cmap, optimization_level=0)
```

### What should happen?

It should work

### Any suggestions?

_No response_


Discussion:
The immediate issue is that the `2` is an int, so Python will allow you to raise it to an arbitrary int power, but floats only have finite precision.  The magic number here is actually 1024, not 1000, because the max float64 exponent is 1023, but that's largely irrelevant.

We can prevent an error being raised by changing the line to `lam = np.pi * (2.0 ** (k - j))`, which forces us to use floating-point operations and Python doesn't trap on FP underflow (only overflow).  That doesn't actually solve the problem properly, though.  To some degree, this is kind of unsolvable in current precision - we can't actually represent the rotation angle needed there in double-precision floating point.  The QFT calls for `pi * 2**-1080` here, but in double-precision maths that's exactly 0.
Yes, you would expect it to auto truncate terms that are smaller than something like floating point zero. 
That's fine if that's a final number, but once you put the circuit in place, it's not actually going to do the right thing anymore (in an ideal experimental world where we can actually control angles of 10^-315 of a radian lol).

I'll swap it over to the underflow form, but I'll put in a warning catch as well.

Resolving patch:
diff --git a/qiskit/circuit/library/basis_change/qft.py b/qiskit/circuit/library/basis_change/qft.py
--- a/qiskit/circuit/library/basis_change/qft.py
+++ b/qiskit/circuit/library/basis_change/qft.py
@@ -13,6 +13,7 @@
 """Quantum Fourier Transform Circuit."""
 
 from typing import Optional
+import warnings
 import numpy as np
 
 from qiskit.circuit import QuantumCircuit, QuantumRegister
@@ -229,6 +230,22 @@ def inverse(self) -> "QFT":
         inverted._inverse = not self._inverse
         return inverted
 
+    def _warn_if_precision_loss(self):
+        """Issue a warning if constructing the circuit will lose precision.
+
+        If we need an angle smaller than ``pi * 2**-1022``, we start to lose precision by going into
+        the subnormal numbers.  We won't lose _all_ precision until an exponent of about 1075, but
+        beyond 1022 we're using fractional bits to represent leading zeros."""
+        max_num_entanglements = self.num_qubits - self.approximation_degree - 1
+        if max_num_entanglements > -np.finfo(float).minexp:  # > 1022 for doubles.
+            warnings.warn(
+                "precision loss in QFT."
+                f" The rotation needed to represent {max_num_entanglements} entanglements"
+                " is smaller than the smallest normal floating-point number.",
+                category=RuntimeWarning,
+                stacklevel=3,
+            )
+
     def _check_configuration(self, raise_on_failure: bool = True) -> bool:
         """Check if the current configuration is valid."""
         valid = True
@@ -236,7 +253,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             valid = False
             if raise_on_failure:
                 raise AttributeError("The number of qubits has not been set.")
-
+        self._warn_if_precision_loss()
         return valid
 
     def _build(self) -> None:
@@ -256,7 +273,9 @@ def _build(self) -> None:
             circuit.h(j)
             num_entanglements = max(0, j - max(0, self.approximation_degree - (num_qubits - j - 1)))
             for k in reversed(range(j - num_entanglements, j)):
-                lam = np.pi / (2 ** (j - k))
+                # Use negative exponents so that the angle safely underflows to zero, rather than
+                # using a temporary variable that overflows to infinity in the worst case.
+                lam = np.pi * (2.0 ** (k - j))
                 circuit.cp(lam, j, k)
 
             if self.insert_barriers:


Test patch:
diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py
--- a/test/python/circuit/library/test_qft.py
+++ b/test/python/circuit/library/test_qft.py
@@ -13,6 +13,7 @@
 """Test library of QFT circuits."""
 
 import unittest
+import warnings
 import numpy as np
 from ddt import ddt, data, unpack
 
@@ -167,6 +168,43 @@ def test_name_after_inverting(self):
         with self.subTest(msg="inverse as kwarg"):
             self.assertEqual(QFT(1, inverse=True).name, "IQFT")
 
+    def test_warns_if_too_large(self):
+        """Test that a warning is issued if the user tries to make a circuit that would need to
+        represent angles smaller than the smallest normal double-precision floating-point number.
+        It's too slow to actually let QFT construct a 1050+ qubit circuit for such a simple test, so
+        we temporarily prevent QuantumCircuits from being created in order to short-circuit the QFT
+        builder."""
+
+        class SentinelException(Exception):
+            """Dummy exception that raises itself as soon as it is created."""
+
+            def __init__(self, *_args, **_kwargs):
+                super().__init__()
+                raise self
+
+        # We don't want to issue a warning on mutation until we know that the values are
+        # finalised; this is because a user might want to mutate the number of qubits and the
+        # approximation degree.  In these cases, wait until we try to build the circuit.
+        with warnings.catch_warnings(record=True) as caught_warnings:
+            warnings.filterwarnings(
+                "always",
+                category=RuntimeWarning,
+                module=r"qiskit\..*",
+                message=r".*precision loss in QFT.*",
+            )
+            qft = QFT()
+            # Even with the approximation this will trigger the warning.
+            qft.num_qubits = 1080
+            qft.approximation_degree = 20
+        self.assertFalse(caught_warnings)
+
+        # Short-circuit the build method so it exits after input validation, but without actually
+        # spinning the CPU to build a huge, useless object.
+        with unittest.mock.patch("qiskit.circuit.QuantumCircuit.__init__", SentinelException):
+            with self.assertWarnsRegex(RuntimeWarning, "precision loss in QFT"):
+                with self.assertRaises(SentinelException):
+                    qft._build()
+
 
 if __name__ == "__main__":
     unittest.main()