Coverage Report

Created: 2025-12-31 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}