1#![deny(missing_docs)]
77#![cfg_attr(not(feature = "std"), no_std)]
78#![forbid(unsafe_code)]
79
80#[cfg(not(feature = "std"))]
81extern crate libm;
82
83mod dimension;
88mod macros;
89mod quantity;
90mod unit;
91
92pub use dimension::{Dimension, Dimensionless, DivDim};
97pub use quantity::Quantity;
98pub use unit::{Per, Simplify, Unit, Unitless};
99
100#[cfg(feature = "serde")]
101pub use quantity::serde_with_unit;
102
103pub mod units;
112
113pub use units::angular;
114pub use units::frequency;
115pub use units::length;
116pub use units::mass;
117pub use units::power;
118pub use units::time;
119pub use units::unitless;
120pub use units::velocity;
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[derive(Debug)]
130 pub enum TestDim {}
131 impl Dimension for TestDim {}
132
133 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
134 pub enum TestUnit {}
135 impl Unit for TestUnit {
136 const RATIO: f64 = 1.0;
137 type Dim = TestDim;
138 const SYMBOL: &'static str = "tu";
139 }
140 impl core::fmt::Display for Quantity<TestUnit> {
141 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142 write!(f, "{} tu", self.value())
143 }
144 }
145
146 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
147 pub enum DoubleTestUnit {}
148 impl Unit for DoubleTestUnit {
149 const RATIO: f64 = 2.0;
150 type Dim = TestDim;
151 const SYMBOL: &'static str = "dtu";
152 }
153 impl core::fmt::Display for Quantity<DoubleTestUnit> {
154 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
155 write!(f, "{} dtu", self.value())
156 }
157 }
158
159 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
160 pub enum HalfTestUnit {}
161 impl Unit for HalfTestUnit {
162 const RATIO: f64 = 0.5;
163 type Dim = TestDim;
164 const SYMBOL: &'static str = "htu";
165 }
166 impl core::fmt::Display for Quantity<HalfTestUnit> {
167 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
168 write!(f, "{} htu", self.value())
169 }
170 }
171
172 type TU = Quantity<TestUnit>;
173 type Dtu = Quantity<DoubleTestUnit>;
174
175 #[test]
180 fn quantity_new_and_value() {
181 let q = TU::new(42.0);
182 assert_eq!(q.value(), 42.0);
183 }
184
185 #[test]
186 fn quantity_nan_constant() {
187 assert!(TU::NAN.value().is_nan());
188 }
189
190 #[test]
191 fn quantity_abs() {
192 assert_eq!(TU::new(-5.0).abs().value(), 5.0);
193 assert_eq!(TU::new(5.0).abs().value(), 5.0);
194 assert_eq!(TU::new(0.0).abs().value(), 0.0);
195 }
196
197 #[test]
198 fn quantity_from_f64() {
199 let q: TU = 123.456.into();
200 assert_eq!(q.value(), 123.456);
201 }
202
203 #[test]
208 fn quantity_conversion_to_same_unit() {
209 let q = TU::new(10.0);
210 let converted = q.to::<TestUnit>();
211 assert_eq!(converted.value(), 10.0);
212 }
213
214 #[test]
215 fn quantity_conversion_to_different_unit() {
216 let q = TU::new(10.0);
219 let converted = q.to::<DoubleTestUnit>();
220 assert!((converted.value() - 5.0).abs() < 1e-12);
221 }
222
223 #[test]
224 fn quantity_conversion_roundtrip() {
225 let original = TU::new(100.0);
226 let converted = original.to::<DoubleTestUnit>();
227 let back = converted.to::<TestUnit>();
228 assert!((back.value() - original.value()).abs() < 1e-12);
229 }
230
231 #[test]
236 fn const_add() {
237 let a = TU::new(3.0);
238 let b = TU::new(7.0);
239 assert_eq!(a.add(b).value(), 10.0);
240 }
241
242 #[test]
243 fn const_sub() {
244 let a = TU::new(10.0);
245 let b = TU::new(3.0);
246 assert_eq!(a.sub(b).value(), 7.0);
247 }
248
249 #[test]
250 fn const_mul() {
251 let a = TU::new(4.0);
252 let b = TU::new(5.0);
253 assert_eq!(Quantity::mul(&a, b).value(), 20.0);
254 }
255
256 #[test]
257 fn const_div() {
258 let a = TU::new(20.0);
259 let b = TU::new(4.0);
260 assert_eq!(Quantity::div(&a, b).value(), 5.0);
261 }
262
263 #[test]
264 fn const_min() {
265 let a = TU::new(5.0);
266 let b = TU::new(3.0);
267 assert_eq!(a.min(b).value(), 3.0);
268 assert_eq!(b.min(a).value(), 3.0);
269 }
270
271 #[test]
276 fn operator_add() {
277 let a = TU::new(3.0);
278 let b = TU::new(7.0);
279 assert_eq!((a + b).value(), 10.0);
280 }
281
282 #[test]
283 fn operator_sub() {
284 let a = TU::new(10.0);
285 let b = TU::new(3.0);
286 assert_eq!((a - b).value(), 7.0);
287 }
288
289 #[test]
290 fn operator_mul_by_f64() {
291 let q = TU::new(5.0);
292 assert_eq!((q * 3.0).value(), 15.0);
293 assert_eq!((3.0 * q).value(), 15.0);
294 }
295
296 #[test]
297 fn operator_div_by_f64() {
298 let q = TU::new(15.0);
299 assert_eq!((q / 3.0).value(), 5.0);
300 }
301
302 #[test]
303 fn operator_neg() {
304 let q = TU::new(5.0);
305 assert_eq!((-q).value(), -5.0);
306 assert_eq!((-(-q)).value(), 5.0);
307 }
308
309 #[test]
310 fn operator_rem() {
311 let q = TU::new(10.0);
312 assert_eq!((q % 3.0).value(), 1.0);
313 }
314
315 #[test]
320 fn operator_add_assign() {
321 let mut q = TU::new(5.0);
322 q += TU::new(3.0);
323 assert_eq!(q.value(), 8.0);
324 }
325
326 #[test]
327 fn operator_sub_assign() {
328 let mut q = TU::new(10.0);
329 q -= TU::new(3.0);
330 assert_eq!(q.value(), 7.0);
331 }
332
333 #[test]
334 fn operator_div_assign() {
335 let mut q = TU::new(20.0);
336 q /= TU::new(4.0);
337 assert_eq!(q.value(), 5.0);
338 }
339
340 #[test]
345 fn partial_eq_f64() {
346 let q = TU::new(5.0);
347 assert!(q == 5.0);
348 assert!(!(q == 4.0));
349 }
350
351 #[test]
356 fn division_creates_per_type() {
357 let num = TU::new(100.0);
358 let den = Dtu::new(20.0);
359 let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = num / den;
360 assert!((ratio.value() - 5.0).abs() < 1e-12);
361 }
362
363 #[test]
364 fn per_ratio_conversion() {
365 let v1: Quantity<Per<DoubleTestUnit, TestUnit>> = Quantity::new(10.0);
366 let v2: Quantity<Per<TestUnit, TestUnit>> = v1.to();
367 assert!((v2.value() - 20.0).abs() < 1e-12);
368 }
369
370 #[test]
371 fn per_multiplication_recovers_numerator() {
372 let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
373 let time = Dtu::new(4.0);
374 let result: TU = rate * time;
375 assert!((result.value() - 20.0).abs() < 1e-12);
376 }
377
378 #[test]
379 fn per_multiplication_commutative() {
380 let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
381 let time = Dtu::new(4.0);
382 let result1: TU = rate * time;
383 let result2: TU = time * rate;
384 assert!((result1.value() - result2.value()).abs() < 1e-12);
385 }
386
387 #[test]
392 fn simplify_per_u_u_to_unitless() {
393 let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.23456);
394 let unitless: Quantity<Unitless> = ratio.simplify();
395 assert!((unitless.value() - 1.23456).abs() < 1e-12);
396 }
397
398 #[test]
399 fn simplify_per_n_per_n_d_to_d() {
400 let q: Quantity<Per<TestUnit, Per<TestUnit, DoubleTestUnit>>> = Quantity::new(7.5);
401 let simplified: Dtu = q.simplify();
402 assert!((simplified.value() - 7.5).abs() < 1e-12);
403 }
404
405 #[test]
410 fn per_u_u_asin() {
411 let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.5);
412 let result = ratio.asin();
413 assert!((result - 0.5_f64.asin()).abs() < 1e-12);
414 }
415
416 #[test]
417 fn per_u_u_asin_boundary_values() {
418 let one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.0);
419 assert!((one.asin() - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
420
421 let neg_one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(-1.0);
422 assert!((neg_one.asin() - (-core::f64::consts::FRAC_PI_2)).abs() < 1e-12);
423
424 let zero: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.0);
425 assert!((zero.asin() - 0.0).abs() < 1e-12);
426 }
427
428 #[test]
433 fn display_simple_quantity() {
434 let q = TU::new(42.5);
435 let s = format!("{}", q);
436 assert_eq!(s, "42.5 tu");
437 }
438
439 #[test]
440 fn display_per_quantity() {
441 let q: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(2.5);
442 let s = format!("{}", q);
443 assert_eq!(s, "2.5 tu/dtu");
444 }
445
446 #[test]
447 fn display_negative_value() {
448 let q = TU::new(-99.9);
449 let s = format!("{}", q);
450 assert_eq!(s, "-99.9 tu");
451 }
452
453 #[test]
458 fn edge_case_zero() {
459 let zero = TU::new(0.0);
460 assert_eq!(zero.value(), 0.0);
461 assert_eq!((-zero).value(), 0.0);
462 assert_eq!(zero.abs().value(), 0.0);
463 }
464
465 #[test]
466 fn edge_case_negative_values() {
467 let neg = TU::new(-10.0);
468 let pos = TU::new(5.0);
469
470 assert_eq!((neg + pos).value(), -5.0);
471 assert_eq!((neg - pos).value(), -15.0);
472 assert_eq!((neg * 2.0).value(), -20.0);
473 assert_eq!(neg.abs().value(), 10.0);
474 }
475
476 #[test]
477 fn edge_case_large_values() {
478 let large = TU::new(1e100);
479 let small = TU::new(1e-100);
480 assert_eq!(large.value(), 1e100);
481 assert_eq!(small.value(), 1e-100);
482 }
483
484 #[test]
485 fn edge_case_infinity() {
486 let inf = TU::new(f64::INFINITY);
487 let neg_inf = TU::new(f64::NEG_INFINITY);
488
489 assert!(inf.value().is_infinite());
490 assert!(neg_inf.value().is_infinite());
491 assert_eq!(inf.value().signum(), 1.0);
492 assert_eq!(neg_inf.value().signum(), -1.0);
493 }
494
495 #[cfg(feature = "serde")]
500 mod serde_tests {
501 use super::*;
502 use serde::{Deserialize, Serialize};
503
504 #[test]
505 fn serialize_quantity() {
506 let q = TU::new(42.5);
507 let json = serde_json::to_string(&q).unwrap();
508 assert_eq!(json, "42.5");
509 }
510
511 #[test]
512 fn deserialize_quantity() {
513 let json = "42.5";
514 let q: TU = serde_json::from_str(json).unwrap();
515 assert_eq!(q.value(), 42.5);
516 }
517
518 #[test]
519 fn serde_roundtrip() {
520 let original = TU::new(123.456);
521 let json = serde_json::to_string(&original).unwrap();
522 let restored: TU = serde_json::from_str(&json).unwrap();
523 assert!((restored.value() - original.value()).abs() < 1e-12);
524 }
525
526 #[derive(Serialize, Deserialize, Debug)]
531 struct TestStruct {
532 #[serde(with = "crate::serde_with_unit")]
533 distance: TU,
534 }
535
536 #[test]
537 fn serde_with_unit_serialize() {
538 let data = TestStruct {
539 distance: TU::new(42.5),
540 };
541 let json = serde_json::to_string(&data).unwrap();
542 assert!(json.contains("\"value\""));
543 assert!(json.contains("\"unit\""));
544 assert!(json.contains("42.5"));
545 assert!(json.contains("\"tu\""));
546 }
547
548 #[test]
549 fn serde_with_unit_deserialize() {
550 let json = r#"{"distance":{"value":42.5,"unit":"tu"}}"#;
551 let data: TestStruct = serde_json::from_str(json).unwrap();
552 assert_eq!(data.distance.value(), 42.5);
553 }
554
555 #[test]
556 fn serde_with_unit_deserialize_no_unit_field() {
557 let json = r#"{"distance":{"value":42.5}}"#;
559 let data: TestStruct = serde_json::from_str(json).unwrap();
560 assert_eq!(data.distance.value(), 42.5);
561 }
562
563 #[test]
564 fn serde_with_unit_deserialize_wrong_unit() {
565 let json = r#"{"distance":{"value":42.5,"unit":"wrong"}}"#;
566 let result: Result<TestStruct, _> = serde_json::from_str(json);
567 assert!(result.is_err());
568 let err_msg = result.unwrap_err().to_string();
569 assert!(err_msg.contains("unit mismatch") || err_msg.contains("expected"));
570 }
571
572 #[test]
573 fn serde_with_unit_deserialize_missing_value() {
574 let json = r#"{"distance":{"unit":"tu"}}"#;
575 let result: Result<TestStruct, _> = serde_json::from_str(json);
576 assert!(result.is_err());
577 let err_msg = result.unwrap_err().to_string();
578 assert!(err_msg.contains("missing field") || err_msg.contains("value"));
579 }
580
581 #[test]
582 fn serde_with_unit_deserialize_duplicate_value() {
583 let json = r#"{"distance":{"value":42.5,"value":100.0,"unit":"tu"}}"#;
584 let result: Result<TestStruct, _> = serde_json::from_str(json);
585 let _ = result;
588 }
589
590 #[test]
591 fn serde_with_unit_deserialize_duplicate_unit() {
592 let json = r#"{"distance":{"value":42.5,"unit":"tu","unit":"tu"}}"#;
593 let result: Result<TestStruct, _> = serde_json::from_str(json);
594 let _ = result;
596 }
597
598 #[test]
599 fn serde_with_unit_deserialize_invalid_format() {
600 let json = r#"{"distance":"not_an_object"}"#;
602 let result: Result<TestStruct, _> = serde_json::from_str(json);
603 assert!(result.is_err());
604 }
605
606 #[test]
607 fn serde_with_unit_deserialize_array() {
608 let json = r#"{"distance":[42.5, "tu"]}"#;
610 let result: Result<TestStruct, _> = serde_json::from_str(json);
611 assert!(result.is_err());
612 }
613
614 #[test]
615 fn serde_with_unit_roundtrip() {
616 let original = TestStruct {
617 distance: TU::new(123.456),
618 };
619 let json = serde_json::to_string(&original).unwrap();
620 let restored: TestStruct = serde_json::from_str(&json).unwrap();
621 assert!((restored.distance.value() - original.distance.value()).abs() < 1e-12);
622 }
623
624 #[test]
625 fn serde_with_unit_special_values() {
626 let test_large = TestStruct {
630 distance: TU::new(1e100),
631 };
632 let json = serde_json::to_string(&test_large).unwrap();
633 let restored: TestStruct = serde_json::from_str(&json).unwrap();
634 assert!((restored.distance.value() - 1e100).abs() < 1e88);
635
636 let test_small = TestStruct {
637 distance: TU::new(-1e-100),
638 };
639 let json = serde_json::to_string(&test_small).unwrap();
640 let restored: TestStruct = serde_json::from_str(&json).unwrap();
641 assert!((restored.distance.value() + 1e-100).abs() < 1e-112);
642 }
643 }
644}