rustpython_literal/
complex.rs1use crate::float;
2use alloc::borrow::ToOwned;
3use alloc::string::{String, ToString};
4
5fn component_to_string(value: f64) -> String {
15 let lit = alloc::format!("{value:e}");
16 if let Some(position) = lit.find('e') {
17 let significand = &lit[..position];
18 let exponent = lit[position + 1..].parse::<i32>().unwrap();
19 if exponent < 16 && exponent > -5 {
20 float::prefer_cpython_tie_repr(value.to_string(), value)
23 } else {
24 float::prefer_cpython_tie_repr(alloc::format!("{significand}e{exponent:+#03}"), value)
25 }
26 } else {
27 let mut s = value.to_string();
30 s.make_ascii_lowercase();
31 s
32 }
33}
34
35#[must_use]
37pub fn to_string(re: f64, im: f64) -> String {
38 let mut im_part = component_to_string(im);
39 im_part.push('j');
40
41 let re_part = if re == 0.0 {
43 if re.is_sign_positive() {
44 return im_part;
45 }
46 "-0".to_owned()
47 } else {
48 component_to_string(re)
49 };
50 let mut result =
51 String::with_capacity(re_part.len() + im_part.len() + 2 + im.is_sign_positive() as usize);
52 result.push('(');
53 result.push_str(&re_part);
54 if im.is_sign_positive() || im.is_nan() {
55 result.push('+');
56 }
57 result.push_str(&im_part);
58 result.push(')');
59 result
60}
61
62pub fn parse_str(s: &str) -> Option<(f64, f64)> {
66 let s = s.trim();
67 let s = match s.strip_prefix('(') {
69 None => s,
70 Some(s) => s.strip_suffix(')')?.trim(),
71 };
72
73 if s.contains(char::is_whitespace) {
78 return None;
79 }
80
81 let value = match s.strip_suffix(|c| c == 'j' || c == 'J') {
82 None => (float::parse_str(s)?, 0.0),
83 Some(mut s) => {
84 let mut real = 0.0;
85 for (i, w) in s.as_bytes().windows(2).enumerate() {
87 if (w[1] == b'+' || w[1] == b'-') && !(w[0] == b'e' || w[0] == b'E') {
88 real = float::parse_str(&s[..=i])?;
89 s = &s[i + 1..];
90 break;
91 }
92 }
93
94 let imag = match s {
95 "" | "+" => 1.0,
97 "-" => -1.0,
99 s => float::parse_str(s)?,
100 };
101
102 (real, imag)
103 }
104 };
105 Some(value)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn parse_rejects_internal_whitespace() {
114 for s in [
117 "1 +2j", "1 2j", "1 +2 j", "+ 1j", "1.5 j", "(1 +2j)", "2 -3j",
118 ] {
119 assert_eq!(parse_str(s), None, "{s:?} must not parse");
120 }
121 }
122
123 #[test]
124 fn parse_allows_surrounding_and_paren_whitespace() {
125 for s in [" 1+2j ", " (1+2j) ", "( 1+2j )"] {
126 assert_eq!(parse_str(s), Some((1.0, 2.0)), "{s:?}");
127 }
128 }
129
130 #[test]
131 fn parse_basic() {
132 assert_eq!(parse_str("1"), Some((1.0, 0.0)));
133 assert_eq!(parse_str("1j"), Some((0.0, 1.0)));
134 assert_eq!(parse_str("j"), Some((0.0, 1.0)));
135 assert_eq!(parse_str("-j"), Some((0.0, -1.0)));
136 assert_eq!(parse_str("1+2j"), Some((1.0, 2.0)));
137 assert_eq!(parse_str("1e5j"), Some((0.0, 1e5)));
138 assert_eq!(parse_str("1_000"), Some((1000.0, 0.0)));
139 assert_eq!(parse_str(""), None);
140 assert_eq!(parse_str("abc"), None);
141 }
142}