1use serde::Serialize;
2
3use crate::execution::types::NumericType;
4use crate::pretty::NumericSuffix;
5
6pub fn human_display_number(value: f64, ty: NumericType) -> String {
9 match ty {
10 NumericType::Known(unit_type) => format!("{value}: number({unit_type})"),
11 NumericType::Default { len, angle } => format!("{value} (no units, defaulting to {len} or {angle})"),
12 NumericType::Unknown => format!("{value} (number with unknown units)"),
13 NumericType::Any => format!("{value} (number with any units)"),
14 }
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, thiserror::Error)]
18#[serde(tag = "type")]
19pub enum FormatNumericSuffixError {
20 #[error("Invalid numeric suffix: {0}")]
21 Invalid(NumericSuffix),
22}
23
24pub fn format_number_literal(
29 value: f64,
30 suffix: NumericSuffix,
31 decimals: Option<usize>,
32) -> Result<String, FormatNumericSuffixError> {
33 let d = decimals.unwrap_or(2);
35 let factor = 10_f64.powi(d as i32);
37 let rounded = (value * factor).round() / factor;
38 let rounded = normalize_negative_zero(rounded);
39 let formatted = if rounded.fract().abs() < 1e-10 {
41 format!("{:.0}", rounded)
43 } else {
44 let with_decimals = format!("{rounded:.d$}");
46 with_decimals.trim_end_matches('0').trim_end_matches('.').to_string()
47 };
48
49 match suffix {
50 NumericSuffix::Length | NumericSuffix::Angle | NumericSuffix::Unknown => {
54 Err(FormatNumericSuffixError::Invalid(suffix))
55 }
56 NumericSuffix::None
57 | NumericSuffix::Count
58 | NumericSuffix::Mm
59 | NumericSuffix::Cm
60 | NumericSuffix::M
61 | NumericSuffix::Inch
62 | NumericSuffix::Ft
63 | NumericSuffix::Yd
64 | NumericSuffix::Deg
65 | NumericSuffix::Rad => Ok(format!("{formatted}{suffix}")),
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, thiserror::Error)]
70#[serde(tag = "type")]
71pub enum FormatNumericTypeError {
72 #[error("Invalid numeric type: {0:?}")]
73 Invalid(NumericType),
74}
75
76pub fn format_number_value(value: f64, ty: NumericType) -> Result<String, FormatNumericTypeError> {
81 let value = normalize_negative_zero(value);
82 match ty {
83 NumericType::Default { .. } => Ok(value.to_string()),
84 NumericType::Unknown | NumericType::Any => Err(FormatNumericTypeError::Invalid(ty)),
88 NumericType::Known(unit_type) => unit_type
89 .to_suffix()
90 .map(|suffix| format!("{value}{suffix}"))
91 .ok_or(FormatNumericTypeError::Invalid(ty)),
92 }
93}
94
95fn normalize_negative_zero(value: f64) -> f64 {
96 if value == 0.0 { 0.0 } else { value }
97}
98
99#[cfg(test)]
100mod tests {
101 use kittycad_modeling_cmds::units::UnitAngle;
102 use kittycad_modeling_cmds::units::UnitLength;
103 use pretty_assertions::assert_eq;
104
105 use super::*;
106 use crate::execution::types::UnitType;
107
108 #[test]
109 fn test_human_display_number() {
110 assert_eq!(
111 human_display_number(1.0, NumericType::Known(UnitType::Count)),
112 "1: number(Count)"
113 );
114 assert_eq!(
115 human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Meters))),
116 "1: number(m)"
117 );
118 assert_eq!(
119 human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Millimeters))),
120 "1: number(mm)"
121 );
122 assert_eq!(
123 human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Inches))),
124 "1: number(in)"
125 );
126 assert_eq!(
127 human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLength::Feet))),
128 "1: number(ft)"
129 );
130 assert_eq!(
131 human_display_number(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Degrees))),
132 "1: number(deg)"
133 );
134 assert_eq!(
135 human_display_number(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Radians))),
136 "1: number(rad)"
137 );
138 assert_eq!(
139 human_display_number(
140 1.0,
141 NumericType::Default {
142 len: UnitLength::Millimeters,
143 angle: UnitAngle::Degrees,
144 }
145 ),
146 "1 (no units, defaulting to mm or deg)"
147 );
148 assert_eq!(
149 human_display_number(
150 1.0,
151 NumericType::Default {
152 len: UnitLength::Feet,
153 angle: UnitAngle::Radians,
154 }
155 ),
156 "1 (no units, defaulting to ft or rad)"
157 );
158 assert_eq!(
159 human_display_number(1.0, NumericType::Unknown),
160 "1 (number with unknown units)"
161 );
162 assert_eq!(human_display_number(1.0, NumericType::Any), "1 (number with any units)");
163 }
164
165 #[test]
166 fn test_format_number_literal() {
167 assert_eq!(
168 format_number_literal(1.0, NumericSuffix::Length, None),
169 Err(FormatNumericSuffixError::Invalid(NumericSuffix::Length))
170 );
171 assert_eq!(
172 format_number_literal(1.0, NumericSuffix::Angle, None),
173 Err(FormatNumericSuffixError::Invalid(NumericSuffix::Angle))
174 );
175 assert_eq!(
176 format_number_literal(1.0, NumericSuffix::None, None),
177 Ok("1".to_owned())
178 );
179 assert_eq!(
180 format_number_literal(1.0, NumericSuffix::Count, None),
181 Ok("1_".to_owned())
182 );
183 assert_eq!(
184 format_number_literal(1.0, NumericSuffix::Mm, None),
185 Ok("1mm".to_owned())
186 );
187 assert_eq!(
188 format_number_literal(1.0, NumericSuffix::Cm, None),
189 Ok("1cm".to_owned())
190 );
191 assert_eq!(format_number_literal(1.0, NumericSuffix::M, None), Ok("1m".to_owned()));
192 assert_eq!(
193 format_number_literal(1.0, NumericSuffix::Inch, None),
194 Ok("1in".to_owned())
195 );
196 assert_eq!(
197 format_number_literal(1.0, NumericSuffix::Ft, None),
198 Ok("1ft".to_owned())
199 );
200 assert_eq!(
201 format_number_literal(1.0, NumericSuffix::Yd, None),
202 Ok("1yd".to_owned())
203 );
204 assert_eq!(
205 format_number_literal(1.0, NumericSuffix::Deg, None),
206 Ok("1deg".to_owned())
207 );
208 assert_eq!(
209 format_number_literal(1.0, NumericSuffix::Rad, None),
210 Ok("1rad".to_owned())
211 );
212 assert_eq!(
213 format_number_literal(1.23456, NumericSuffix::None, Some(4)),
214 Ok("1.2346".to_owned())
215 );
216 assert_eq!(
217 format_number_literal(1.2, NumericSuffix::None, Some(4)),
218 Ok("1.2".to_owned())
219 );
220 assert_eq!(
221 format_number_literal(1.0, NumericSuffix::Unknown, None),
222 Err(FormatNumericSuffixError::Invalid(NumericSuffix::Unknown))
223 );
224 assert_eq!(
225 format_number_literal(-0.0, NumericSuffix::Mm, None),
226 Ok("0mm".to_owned())
227 );
228 }
229
230 #[test]
231 fn test_format_number_value() {
232 assert_eq!(
233 format_number_value(
234 1.0,
235 NumericType::Default {
236 len: UnitLength::Millimeters,
237 angle: UnitAngle::Degrees,
238 }
239 ),
240 Ok("1".to_owned())
241 );
242 assert_eq!(
243 format_number_value(1.0, NumericType::Known(UnitType::GenericLength)),
244 Err(FormatNumericTypeError::Invalid(NumericType::Known(
245 UnitType::GenericLength
246 )))
247 );
248 assert_eq!(
249 format_number_value(1.0, NumericType::Known(UnitType::GenericAngle)),
250 Err(FormatNumericTypeError::Invalid(NumericType::Known(
251 UnitType::GenericAngle
252 )))
253 );
254 assert_eq!(
255 format_number_value(1.0, NumericType::Known(UnitType::Count)),
256 Ok("1_".to_owned())
257 );
258 assert_eq!(
259 format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Millimeters))),
260 Ok("1mm".to_owned())
261 );
262 assert_eq!(
263 format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Centimeters))),
264 Ok("1cm".to_owned())
265 );
266 assert_eq!(
267 format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Meters))),
268 Ok("1m".to_owned())
269 );
270 assert_eq!(
271 format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Inches))),
272 Ok("1in".to_owned())
273 );
274 assert_eq!(
275 format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Feet))),
276 Ok("1ft".to_owned())
277 );
278 assert_eq!(
279 format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLength::Yards))),
280 Ok("1yd".to_owned())
281 );
282 assert_eq!(
283 format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Degrees))),
284 Ok("1deg".to_owned())
285 );
286 assert_eq!(
287 format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Radians))),
288 Ok("1rad".to_owned())
289 );
290 assert_eq!(
291 format_number_value(1.0, NumericType::Unknown),
292 Err(FormatNumericTypeError::Invalid(NumericType::Unknown))
293 );
294 assert_eq!(
295 format_number_value(1.0, NumericType::Any),
296 Err(FormatNumericTypeError::Invalid(NumericType::Any))
297 );
298 assert_eq!(
299 format_number_value(
300 -0.0,
301 NumericType::Default {
302 len: UnitLength::Millimeters,
303 angle: UnitAngle::Degrees,
304 }
305 ),
306 Ok("0".to_owned())
307 );
308 }
309}