Repository: Qiskit/qiskit

Issue:
Scientific notation in QASM output causes export to other platform to fail
### Environment

- **Qiskit version**: {'qiskit-terra': '0.24.0', 'qiskit-aer': '0.12.0', 'qiskit-ignis': '0.7.1', 'qiskit-ibmq-provider': '0.20.2', 'qiskit': '0.43.0', 'qiskit-nature': None, 'qiskit-finance': None, 'qiskit-optimization': None, 'qiskit-machine-learning': None}
- **Python version**: 3.10.6
- **Operating system**: Linux Mint

### What is happening?

Exporting a circuit with small rotation gates through circuit.qasm() results in scientific notation being used. This causes issues when importing the QASM code into another quantum computing platform.

### How can we reproduce the issue?

```
from qiskit import QuantumCircuit
qc = QuantumCircuit(1)
qc.rx(0.00001, 0)
print(qc.qasm())
```
output:
```
OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
creg meas[1];
rx(1e-05) q[0];
barrier q[0];
measure q[0] -> meas[0];
```

### What should happen?

output:
```
OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
creg meas[1];
rx(0.00001) q[0];
barrier q[0];
measure q[0] -> meas[0];
```

### Any suggestions?

The conversion from float to string happens in `pi_check`. The call is `pi_check(0.00001, output="qasm", eps=1e-12)` and returns `'1e-05'`.


Discussion:
I'd argue this actually a bug in the qasm 2 parser you're using. Scientific notation is listed in the qasm2 specification as a feature of the language for real numbers. If the other framework is erroring on that input it seems like an issue on their end. See: https://arxiv.org/abs/1707.03429 see page 5, it's in the footnote of scientific calculator features for real numbers.
Fwiw, scientific notation is allowed in OQ2, but due to (what I believe to be) an oversight in the formal grammar in the paper, `1e-5` is still technically not valid - all floats in OQ2 _must_ have a decimal point by the letter of the spec. Our parser enforces that in "strict" mode, but allows it in the default permissive mode because I thought it was a needlessly pedantic distinction, and would lead to situations like this one.

Personally I think the right choice here is to get rid of `pi_check` from the OQ2 exporter - it does nothing but cause problems, really, and it's basically just a Qiskit-blessed numerical error in the output. I can see the function making some sense for visualisations, but not for this. Alternatively, it's possible to change the output format to always use at least 1 decimal point of precision.

Resolving patch:
diff --git a/qiskit/circuit/tools/pi_check.py b/qiskit/circuit/tools/pi_check.py
--- a/qiskit/circuit/tools/pi_check.py
+++ b/qiskit/circuit/tools/pi_check.py
@@ -167,12 +167,11 @@ def normalize(single_inpt):
                 str_out = f"{neg_str}{numer}/{denom}{pi}"
             return str_out
 
-        # Nothing found
-        if ndigits is None:
-            str_out = "{}".format(single_inpt)
-        else:
-            str_out = "{:.{}g}".format(single_inpt, ndigits)
-        return str_out
+        # Nothing found.  The '#' forces a decimal point to be included, which OQ2 needs, but other
+        # formats don't really.
+        if output == "qasm":
+            return f"{single_inpt:#}" if ndigits is None else f"{single_inpt:#.{ndigits}g}"
+        return f"{single_inpt}" if ndigits is None else f"{single_inpt:.{ndigits}g}"
 
     complex_inpt = complex(inpt)
     real, imag = map(normalize, [complex_inpt.real, complex_inpt.imag])


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py
--- a/test/python/circuit/test_circuit_qasm.py
+++ b/test/python/circuit/test_circuit_qasm.py
@@ -843,6 +843,20 @@ def test_empty_barrier(self):
 qreg qr1[2];
 qreg qr2[3];
 barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2];
+"""
+        self.assertEqual(qc.qasm(), expected)
+
+    def test_small_angle_valid(self):
+        """Test that small angles do not get converted to invalid OQ2 floating-point values."""
+        # OQ2 _technically_ requires a decimal point in all floating-point values, even ones that
+        # are followed by an exponent.
+        qc = QuantumCircuit(1)
+        qc.rx(0.000001, 0)
+        expected = """\
+OPENQASM 2.0;
+include "qelib1.inc";
+qreg q[1];
+rx(1.e-06) q[0];
 """
         self.assertEqual(qc.qasm(), expected)