Skip to content

Commit 5eb3e7b

Browse files
committed
Format floating-point values
This commit adds formatting functionality to render floating-point values (e.g. rotation angles) in a way similar to the `g` formatting flag in Python.
1 parent 9c6baac commit 5eb3e7b

3 files changed

Lines changed: 154 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/circuit/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ nom-language.workspace = true
3131
crossterm = "0.29.0"
3232
unicode-width = "0.2"
3333
unicode-segmentation = "1.12"
34+
lexical-core = "1.0.6"
35+
lexical-write-float = "1.0.6"
3436

3537
[dependencies.pyo3]
3638
workspace = true

crates/circuit/src/circuit_drawer.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use approx;
1919
use crossterm::terminal;
2020
use hashbrown::HashSet;
2121
use itertools::{Itertools, MinMaxResult};
22+
use lexical_core::ToLexicalWithOptions;
23+
use lexical_write_float::{self, format::STANDARD};
2224
use pyo3::prelude::*;
2325
use std::fmt::Debug;
2426
use std::ops::Index;
@@ -60,7 +62,7 @@ pub fn draw_circuit(
6062
if approx::abs_diff_eq!(*f, 0.) {
6163
String::new()
6264
} else {
63-
format!("global phase: {}\n", f)
65+
format!("global phase: {}\n", F64UiFormatter::new(5).format(*f))
6466
}
6567
}
6668
Param::ParameterExpression(expr) => {
@@ -669,6 +671,43 @@ impl TextWireElement {
669671
}
670672
}
671673

674+
/// A simple formatter for rendering nicely-truncated floating-point numbers,
675+
/// in a way similar to Python's g or printf's %g format specifiers.
676+
/// Example outputs:
677+
/// F64UiFormatter::new(4).format(1.23456) --> 1.235
678+
/// F64UiFormatter::new(4).format(123.456) --> 123.5
679+
/// F64UiFormatter::new(5).format(12345678f64) --> 1.2346e7
680+
/// F64UiFormatter::new(5).format(-0.00001234) --> -1.234e-5
681+
struct F64UiFormatter {
682+
buffer: Vec<u8>,
683+
options: lexical_write_float::Options,
684+
}
685+
686+
impl F64UiFormatter {
687+
fn new(num_significant_digits: i32) -> Self {
688+
let options = lexical_write_float::Options::builder()
689+
.max_significant_digits(core::num::NonZeroUsize::new(
690+
num_significant_digits as usize,
691+
))
692+
.positive_exponent_break(core::num::NonZeroI32::new(num_significant_digits))
693+
.negative_exponent_break(core::num::NonZeroI32::new(-num_significant_digits + 1))
694+
.trim_floats(true)
695+
.build_strict();
696+
697+
F64UiFormatter {
698+
buffer: vec![0u8; options.buffer_size_const::<f64, STANDARD>()],
699+
options,
700+
}
701+
}
702+
703+
/// Formats the input number based on the formatting options.
704+
/// This Can be called multiple times, but the internal buffer is overwritten on each call.
705+
fn format(&mut self, num: f64) -> &str {
706+
let buf = num.to_lexical_with_options::<STANDARD>(&mut self.buffer, &self.options);
707+
std::str::from_utf8_mut(buf).expect("Byte representation should be valid")
708+
}
709+
}
710+
672711
pub const Q_WIRE: char = '─';
673712
pub const C_WIRE: char = '═';
674713
pub const TOP_CON: char = '┴';
@@ -731,7 +770,11 @@ impl TextDrawer {
731770
StandardInstruction::Delay(delay_unit) => {
732771
match instruction.params_view().first().unwrap() {
733772
Param::Float(duration) => {
734-
format!("Delay({}[{}])", duration, delay_unit)
773+
format!(
774+
"Delay({}[{}])",
775+
F64UiFormatter::new(5).format(*duration),
776+
delay_unit
777+
)
735778
}
736779
Param::ParameterExpression(expr) => {
737780
format!("Delay({}[{}])", expr, delay_unit)
@@ -763,7 +806,7 @@ impl TextDrawer {
763806
.params_view()
764807
.iter()
765808
.map(|param| match param {
766-
Param::Float(f) => f.to_string(),
809+
Param::Float(f) => F64UiFormatter::new(5).format(*f).to_string(),
767810
Param::ParameterExpression(expr) => expr.to_string(),
768811
_ => format!("{:?}", param),
769812
})
@@ -1902,4 +1945,51 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├
19021945
";
19031946
assert_eq!(result, expected.trim_start_matches("\n"));
19041947
}
1948+
1949+
#[cfg(not(miri))]
1950+
#[test]
1951+
fn test_f64_formatting() {
1952+
let qubits = vec![
1953+
ShareableQubit::new_anonymous(),
1954+
ShareableQubit::new_anonymous(),
1955+
];
1956+
let mut circuit = CircuitData::new(Some(qubits), None, Param::Float(12.3)).unwrap();
1957+
1958+
circuit
1959+
.push_standard_gate(StandardGate::RX, &[Param::Float(1.234567)], &[Qubit(0)])
1960+
.unwrap();
1961+
circuit
1962+
.push_standard_gate(StandardGate::RX, &[Param::Float(123.4567)], &[Qubit(0)])
1963+
.unwrap();
1964+
1965+
let expr = ParameterExpression::from_symbol(Symbol::new("ϕ", None, None))
1966+
.mul(&ParameterExpression::from_f64(1.23456))
1967+
.unwrap();
1968+
let param = Param::ParameterExpression(Arc::new(expr));
1969+
circuit
1970+
.push_standard_gate(StandardGate::RY, &[param], &[Qubit(0)])
1971+
.unwrap();
1972+
circuit
1973+
.push_standard_gate(StandardGate::RZ, &[Param::Float(123456789f64)], &[Qubit(1)])
1974+
.unwrap();
1975+
1976+
circuit
1977+
.push_standard_gate(StandardGate::RX, &[Param::Float(0.1234567)], &[Qubit(1)])
1978+
.unwrap();
1979+
circuit
1980+
.push_standard_gate(StandardGate::RX, &[Param::Float(0.0000123456)], &[Qubit(1)])
1981+
.unwrap();
1982+
1983+
let result = draw_circuit(&circuit, true, true, None).unwrap();
1984+
let expected = "
1985+
global phase: 6.0168
1986+
┌────────────┐ ┌────────────┐ ┌───────────────┐
1987+
q_0: ─┤ Rx(1.2346) ├─┤ Rx(123.46) ├─┤ Ry(1.23456*ϕ) ├
1988+
┌┴────────────┴┐├────────────┴┐├───────────────┤
1989+
q_1: ┤ Rz(1.2346e8) ├┤ Rx(0.12346) ├┤ Rx(1.2346e-5) ├
1990+
└──────────────┘└─────────────┘└───────────────┘
1991+
";
1992+
1993+
assert_eq!(result, expected.trim_start_matches("\n"));
1994+
}
19051995
}

0 commit comments

Comments
 (0)