@@ -19,6 +19,8 @@ use approx;
1919use crossterm:: terminal;
2020use hashbrown:: HashSet ;
2121use itertools:: { Itertools , MinMaxResult } ;
22+ use lexical_core:: ToLexicalWithOptions ;
23+ use lexical_write_float:: { self , format:: STANDARD } ;
2224use pyo3:: prelude:: * ;
2325use std:: f64:: consts:: PI ;
2426use std:: fmt:: Debug ;
@@ -63,7 +65,7 @@ pub fn draw_circuit(
6365 } else {
6466 format ! (
6567 "global phase: {}\n " ,
66- format_float_pi ( * f ) . unwrap_or_else ( || f . to_string ( ) )
68+ F64UiFormatter :: new ( 5 ) . format_with_pi ( * f )
6769 )
6870 }
6971 }
@@ -673,6 +675,55 @@ impl TextWireElement {
673675 }
674676}
675677
678+ /// A formatter for UI rendering of floating-point numbers
679+ ///
680+ /// Supports formatting similar to Python's `g` or C printf's `%g` format specifiers
681+ /// as well as formatting of multiples and fractions of pi.
682+ ///
683+ /// Example outputs:
684+ /// ```text
685+ /// F64UiFormatter::new(4).format(1.23456) → 1.235
686+ /// F64UiFormatter::new(4).format(123.456) → 123.5
687+ /// F64UiFormatter::new(5).format(12345678.0) → 1.2346e7
688+ /// F64UiFormatter::new(5).format(-0.00001234) → -1.234e-5
689+ /// F64UiFormatter::new(5).format_with_pi(5π/6) → 5π/6
690+ /// ```
691+ struct F64UiFormatter {
692+ buffer : Vec < u8 > ,
693+ options : lexical_write_float:: Options ,
694+ }
695+
696+ impl F64UiFormatter {
697+ fn new ( num_significant_digits : usize ) -> Self {
698+ let options = lexical_write_float:: Options :: builder ( )
699+ . max_significant_digits ( core:: num:: NonZeroUsize :: new ( num_significant_digits) )
700+ . positive_exponent_break ( core:: num:: NonZeroI32 :: new ( num_significant_digits as i32 ) )
701+ . negative_exponent_break ( core:: num:: NonZeroI32 :: new (
702+ -( num_significant_digits as i32 ) + 1 ,
703+ ) )
704+ . trim_floats ( true )
705+ . build_strict ( ) ;
706+
707+ F64UiFormatter {
708+ buffer : vec ! [ 0u8 ; options. buffer_size_const:: <f64 , STANDARD >( ) ] ,
709+ options,
710+ }
711+ }
712+
713+ /// Formats the input number based on the formatting options.
714+ /// This Can be called multiple times, but the internal buffer is overwritten on each call.
715+ fn format ( & mut self , num : f64 ) -> & str {
716+ let buf = num. to_lexical_with_options :: < STANDARD > ( & mut self . buffer , & self . options ) ;
717+ std:: str:: from_utf8_mut ( buf) . expect ( "Byte representation should be valid" )
718+ }
719+
720+ /// Tries to format the string as a multiple or simple fraction of pi if possible,
721+ /// otherwise falls back to the simpler [F64UiFormatter::format] logic
722+ fn format_with_pi ( & mut self , num : f64 ) -> String {
723+ format_float_pi ( num) . unwrap_or_else ( || self . format ( num) . to_owned ( ) )
724+ }
725+ }
726+
676727pub const Q_WIRE : char = '─' ;
677728pub const C_WIRE : char = '═' ;
678729pub const TOP_CON : char = '┴' ;
@@ -735,7 +786,11 @@ impl TextDrawer {
735786 StandardInstruction :: Delay ( delay_unit) => {
736787 match instruction. params_view ( ) . first ( ) . unwrap ( ) {
737788 Param :: Float ( duration) => {
738- format ! ( "Delay({}[{}])" , duration, delay_unit)
789+ format ! (
790+ "Delay({}[{}])" ,
791+ F64UiFormatter :: new( 5 ) . format( * duration) ,
792+ delay_unit
793+ )
739794 }
740795 Param :: ParameterExpression ( expr) => {
741796 format ! ( "Delay({}[{}])" , expr, delay_unit)
@@ -767,7 +822,9 @@ impl TextDrawer {
767822 . params_view ( )
768823 . iter ( )
769824 . map ( |param| match param {
770- Param :: Float ( f) => format_float_pi ( * f) . unwrap_or_else ( || f. to_string ( ) ) ,
825+ Param :: Float ( f) => {
826+ F64UiFormatter :: new ( 5 ) . format_with_pi ( * f) . to_string ( )
827+ }
771828 Param :: ParameterExpression ( expr) => expr. to_string ( ) ,
772829 _ => format ! ( "{:?}" , param) ,
773830 } )
@@ -2092,6 +2149,60 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├
20922149 assert_eq ! ( result, expected. trim_start_matches( "\n " ) ) ;
20932150 }
20942151
2152+ #[ cfg( not( miri) ) ]
2153+ #[ test]
2154+ fn test_f64_formatting ( ) {
2155+ let qubits = vec ! [
2156+ ShareableQubit :: new_anonymous( ) ,
2157+ ShareableQubit :: new_anonymous( ) ,
2158+ ] ;
2159+ let mut circuit = CircuitData :: new ( Some ( qubits) , None , Param :: Float ( 0.8 * PI ) ) . unwrap ( ) ;
2160+
2161+ circuit
2162+ . push_standard_gate ( StandardGate :: RX , & [ Param :: Float ( 1.234567 ) ] , & [ Qubit ( 0 ) ] )
2163+ . unwrap ( ) ;
2164+ circuit
2165+ . push_standard_gate ( StandardGate :: RX , & [ Param :: Float ( 123.4567 ) ] , & [ Qubit ( 0 ) ] )
2166+ . unwrap ( ) ;
2167+
2168+ let expr = ParameterExpression :: from_symbol ( Symbol :: new ( "ϕ" , None , None ) )
2169+ . mul ( & ParameterExpression :: from_f64 ( 1.23456 ) )
2170+ . unwrap ( ) ;
2171+ let param = Param :: ParameterExpression ( Arc :: new ( expr) ) ;
2172+ circuit
2173+ . push_standard_gate ( StandardGate :: RY , & [ param] , & [ Qubit ( 0 ) ] )
2174+ . unwrap ( ) ;
2175+ circuit
2176+ . push_standard_gate ( StandardGate :: RZ , & [ Param :: Float ( 123456789f64 ) ] , & [ Qubit ( 1 ) ] )
2177+ . unwrap ( ) ;
2178+
2179+ circuit
2180+ . push_standard_gate ( StandardGate :: RX , & [ Param :: Float ( 0.1234567 ) ] , & [ Qubit ( 1 ) ] )
2181+ . unwrap ( ) ;
2182+ circuit
2183+ . push_standard_gate ( StandardGate :: RX , & [ Param :: Float ( 0.0000123456 ) ] , & [ Qubit ( 1 ) ] )
2184+ . unwrap ( ) ;
2185+ circuit
2186+ . push_standard_gate (
2187+ StandardGate :: RX ,
2188+ & [ Param :: Float ( 2.0 / 3.0 * PI ) ] ,
2189+ & [ Qubit ( 1 ) ] ,
2190+ )
2191+ . unwrap ( ) ;
2192+
2193+ let result = draw_circuit ( & circuit, true , true , None ) . unwrap ( ) ;
2194+ let expected = "
2195+ global phase: 4π/5
2196+ ┌────────────┐ ┌────────────┐ ┌───────────────┐
2197+ q_0: ─┤ Rx(1.2346) ├─┤ Rx(123.46) ├─┤ Ry(1.23456*ϕ) ├────────────
2198+ ┌┴────────────┴┐├────────────┴┐├───────────────┤┌──────────┐
2199+ q_1: ┤ Rz(1.2346e8) ├┤ Rx(0.12346) ├┤ Rx(1.2346e-5) ├┤ Rx(2π/3) ├
2200+ └──────────────┘└─────────────┘└───────────────┘└──────────┘
2201+ " ;
2202+
2203+ assert_eq ! ( result, expected. trim_start_matches( "\n " ) ) ;
2204+ }
2205+
20952206 #[ test]
20962207 fn test_format_float_pi ( ) {
20972208 let test_points = [
@@ -2146,4 +2257,25 @@ q_1: ┤ Ry(🎩) ├┤1 ├┤ 💶🔉(🎩) ├┤1 ├
21462257 assert_eq ! ( format_float_pi( test. 0 ) , test. 1 . map( |s| s. to_string( ) ) ) ;
21472258 }
21482259 }
2260+
2261+ #[ test]
2262+ fn test_f64_ui_formatter ( ) {
2263+ let test_data_5_sig_digits = [
2264+ ( -1.23 , "-1.23" ) ,
2265+ ( 1.23456 , "1.2346" ) ,
2266+ ( -12.34567 , "-12.346" ) ,
2267+ ( 123456.78 , "123460" ) ,
2268+ ( -0.0001 , "-0.0001" ) ,
2269+ ( 12.34 * 1_000_000.0 , "1.234e7" ) ,
2270+ ( -0.00001 , "-1e-5" ) ,
2271+ ( 12345678.000001 , "1.2346e7" ) ,
2272+ ( 15.0 * PI / 16.0 , "15π/16" ) ,
2273+ ( -2.0 * PI / 3.0 , "-2π/3" ) ,
2274+ ] ;
2275+
2276+ let mut formatter = F64UiFormatter :: new ( 5 ) ;
2277+ for test in test_data_5_sig_digits {
2278+ assert_eq ! ( test. 1 . to_owned( ) , formatter. format_with_pi( test. 0 ) ) ;
2279+ }
2280+ }
21492281}
0 commit comments