/rust/registry/src/index.crates.io-1949cf8c6b5b557f/fixed_decimal-0.5.6/src/scientific.rs
Line | Count | Source |
1 | | // This file is part of ICU4X. For terms of use, please see the file |
2 | | // called LICENSE at the top level of the ICU4X source tree |
3 | | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
4 | | |
5 | | use core::convert::TryFrom; |
6 | | use core::fmt; |
7 | | |
8 | | use core::str::FromStr; |
9 | | |
10 | | use crate::Error; |
11 | | use crate::FixedDecimal; |
12 | | use crate::FixedInteger; |
13 | | |
14 | | /// A struct containing a [`FixedDecimal`] significand together with an exponent, representing a |
15 | | /// number written in scientific notation, such as 1.729×10³. |
16 | | /// This structure represents any 0s shown in the significand and exponent, |
17 | | /// and an optional sign for both the significand and the exponent. |
18 | | #[derive(Debug, Clone, PartialEq)] |
19 | | pub struct ScientificDecimal { |
20 | | significand: FixedDecimal, |
21 | | exponent: FixedInteger, |
22 | | } |
23 | | |
24 | | impl ScientificDecimal { |
25 | 0 | pub fn from(significand: FixedDecimal, exponent: FixedInteger) -> Self { |
26 | 0 | ScientificDecimal { |
27 | 0 | significand, |
28 | 0 | exponent, |
29 | 0 | } |
30 | 0 | } |
31 | | } |
32 | | |
33 | | /// Render the [`ScientificDecimal`] as a string of ASCII digits with a possible decimal point, |
34 | | /// followed by the letter 'e', and the exponent. |
35 | | /// |
36 | | /// # Examples |
37 | | /// |
38 | | /// ``` |
39 | | /// # use fixed_decimal::FixedDecimal; |
40 | | /// # use fixed_decimal::FixedInteger; |
41 | | /// # use fixed_decimal::ScientificDecimal; |
42 | | /// # use std::str::FromStr; |
43 | | /// # use writeable::assert_writeable_eq; |
44 | | /// # |
45 | | /// assert_writeable_eq!( |
46 | | /// ScientificDecimal::from( |
47 | | /// FixedDecimal::from(1729).multiplied_pow10(-3), |
48 | | /// FixedInteger::from(3) |
49 | | /// ), |
50 | | /// "1.729e3" |
51 | | /// ); |
52 | | /// assert_writeable_eq!( |
53 | | /// ScientificDecimal::from( |
54 | | /// FixedDecimal::from_str("+1.729").unwrap(), |
55 | | /// FixedInteger::from_str("+03").unwrap() |
56 | | /// ), |
57 | | /// "+1.729e+03" |
58 | | /// ); |
59 | | /// ``` |
60 | | impl writeable::Writeable for ScientificDecimal { |
61 | 0 | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
62 | 0 | self.significand.write_to(sink)?; |
63 | 0 | sink.write_char('e')?; |
64 | 0 | self.exponent.write_to(sink) |
65 | 0 | } |
66 | | |
67 | 0 | fn writeable_length_hint(&self) -> writeable::LengthHint { |
68 | 0 | self.significand.writeable_length_hint() + 1 + self.exponent.writeable_length_hint() |
69 | 0 | } |
70 | | } |
71 | | |
72 | | writeable::impl_display_with_writeable!(ScientificDecimal); |
73 | | |
74 | | impl FromStr for ScientificDecimal { |
75 | | type Err = Error; |
76 | 0 | fn from_str(input_str: &str) -> Result<Self, Self::Err> { |
77 | 0 | Self::try_from(input_str.as_bytes()) |
78 | 0 | } |
79 | | } |
80 | | |
81 | | impl TryFrom<&[u8]> for ScientificDecimal { |
82 | | type Error = Error; |
83 | 0 | fn try_from(input_str: &[u8]) -> Result<Self, Self::Error> { |
84 | | // Fixed_Decimal::try_from supports scientific notation; ensure that |
85 | | // we don’t accept something like 1e1E1. Splitting on 'e' ensures that |
86 | | // we disallow 1e1e1. |
87 | 0 | if input_str.contains(&b'E') { |
88 | 0 | return Err(Error::Syntax); |
89 | 0 | } |
90 | 0 | let mut parts = input_str.split(|&c| c == b'e'); |
91 | 0 | let significand = parts.next().ok_or(Error::Syntax)?; |
92 | 0 | let exponent = parts.next().ok_or(Error::Syntax)?; |
93 | 0 | if parts.next().is_some() { |
94 | 0 | return Err(Error::Syntax); |
95 | 0 | } |
96 | 0 | Ok(ScientificDecimal::from( |
97 | 0 | FixedDecimal::try_from(significand)?, |
98 | 0 | FixedInteger::try_from(exponent)?, |
99 | | )) |
100 | 0 | } |
101 | | } |
102 | | |
103 | | #[test] |
104 | | fn test_scientific_syntax_error() { |
105 | | #[derive(Debug)] |
106 | | struct TestCase { |
107 | | pub input_str: &'static str, |
108 | | pub expected_err: Option<Error>, |
109 | | } |
110 | | let cases = [ |
111 | | TestCase { |
112 | | input_str: "5", |
113 | | expected_err: Some(Error::Syntax), |
114 | | }, |
115 | | TestCase { |
116 | | input_str: "-123c4", |
117 | | expected_err: Some(Error::Syntax), |
118 | | }, |
119 | | TestCase { |
120 | | input_str: "-123e", |
121 | | expected_err: Some(Error::Syntax), |
122 | | }, |
123 | | TestCase { |
124 | | input_str: "1e10", |
125 | | expected_err: None, |
126 | | }, |
127 | | TestCase { |
128 | | input_str: "1e1e1", |
129 | | expected_err: Some(Error::Syntax), |
130 | | }, |
131 | | TestCase { |
132 | | input_str: "1e1E1", |
133 | | expected_err: Some(Error::Syntax), |
134 | | }, |
135 | | TestCase { |
136 | | input_str: "1E1e1", |
137 | | expected_err: Some(Error::Syntax), |
138 | | }, |
139 | | TestCase { |
140 | | input_str: "-1e+01", |
141 | | expected_err: None, |
142 | | }, |
143 | | TestCase { |
144 | | input_str: "-1e+1.0", |
145 | | expected_err: Some(Error::Limit), |
146 | | }, |
147 | | TestCase { |
148 | | input_str: "-1e+-1", |
149 | | expected_err: Some(Error::Syntax), |
150 | | }, |
151 | | TestCase { |
152 | | input_str: "123E4", |
153 | | expected_err: Some(Error::Syntax), |
154 | | }, |
155 | | ]; |
156 | | for cas in &cases { |
157 | | match ScientificDecimal::from_str(cas.input_str) { |
158 | | Ok(dec) => { |
159 | | assert_eq!(cas.expected_err, None, "{cas:?}"); |
160 | | assert_eq!(cas.input_str, dec.to_string(), "{cas:?}"); |
161 | | } |
162 | | Err(err) => { |
163 | | assert_eq!(cas.expected_err, Some(err), "{cas:?}"); |
164 | | } |
165 | | } |
166 | | } |
167 | | } |