ohms/
voltage.rs

1use core::{cmp, fmt, ops};
2
3/// Represents a voltage value, stored as whole microvolts (μV) as a signed 64-bit value.
4/// This value can be positive or negative.
5///
6/// **Reminder:** `1000 μV = 1 mV, 1000 mV = 1 V, 1000 V = 1k V`
7///
8/// This is an immutable type. Any math operators return a new `Voltage` value.
9///
10/// # Creating a Voltage value
11/// You can create a `Voltage` value using the `from_micro_volts` method, or using one of the
12/// extension methods on integer and floating-point types.
13///
14/// ```rust
15/// use ohms::prelude::*;
16///
17/// let v1 = Voltage::from_micro_volts(325); // 325μV
18///
19/// // More ergonomic:
20/// let v2 = 900.milli_volts(); // 900mV
21/// let v3 = 12.volts(); // 12V
22/// let v4 = 3.3.volts(); // 3.3V
23/// ```
24///
25/// # Comparing Voltage values
26/// You can compare two `Voltage` values using the `==`, `!=`, `<`, `>`, `<=` and `>=` operators.
27///
28/// ```rust
29/// use ohms::prelude::*;
30///
31/// let v1 = 3.3.volts(); // 3.3V
32/// let v2 = 5.2.volts(); // 5.2V
33///
34/// if v1 > v2 {
35///     println!("{} is greater than {}", v1, v2);
36/// } else {
37///     println!("{} is less than or equal to {}", v1, v2);
38/// }
39/// ```
40///
41/// # Combining Voltage values
42/// You can use the `+` and `-` operators to add and subtract `Voltage` values from each other.
43/// The result is a new `Voltage` value, rounded down to the nearest whole microvolt (μV).
44///
45/// If the result of the operation would overflow or underflow, the operation will panic.
46///
47/// ```rust
48/// use ohms::prelude::*;
49///
50/// let v1 = 3.7.volts(); // 3.7V
51/// let v2 = 9.volts(); // 9V
52///
53/// let sum = v1 + v2; // 12.7V
54/// let diff = v2 - 6.volts(); // 3V
55/// ```
56///
57/// # Scaling Voltage values
58/// You can use the `*` and `/` operators to scale `Voltage` values by an integer or floating-point value.
59/// The result is a new `Voltage` value, rounded down to the nearest whole microvolt (μV).
60///
61/// If the result of operation would overflow or underflow, the operation will panic.
62///
63/// If the result of the operation would be infinite or NaN, the operation will panic.
64///
65/// ```rust
66/// use ohms::prelude::*;
67///
68/// let v1 = 6.volts(); // 6V
69/// let v2 = v1 * 2; // 12V
70///
71/// let v3 = 250.micro_volts(); // 250μV
72/// let v4 = v3 / 2.0; // 125μV
73/// ```
74///
75/// # Converting to other denominations
76/// You can use the `micro_volts`, `milli_volts`, `volts`, and `kilo_volts` methods to convert a `Voltage`
77/// value to a numeric value in the specified denomination.
78///
79/// ```rust
80/// use ohms::prelude::*;
81///
82/// let v1 = 3.3.volts(); // 3.3V
83///
84/// println!("{:.2} V is {:.1} mV", v1.volts(), v1.milli_volts());
85/// ```
86///
87#[derive(Clone, Copy, Debug)]
88pub struct Voltage {
89    raw: i64,
90}
91
92impl Voltage {
93    /// Creates a new `Voltage` from a number of whole microvolts (μV).
94    ///
95    /// It is recommended to use the `micro_volts`, `milli_volts`, `volts`, and `kilo_volts` extension
96    /// methods on integer and floating-point types instead.
97    #[inline]
98    pub const fn from_micro_volts(value: i64) -> Self {
99        Self { raw: value }
100    }
101
102    /// Returns the voltage value in whole microvolts (μV).
103    #[inline]
104    pub fn micro_volts(&self) -> i64 {
105        self.raw
106    }
107
108    /// Returns the voltage value in fractional millivolts (mV).
109    #[inline]
110    pub fn milli_volts(&self) -> f64 {
111        self.raw as f64 / 1_000_f64
112    }
113
114    /// Returns the voltage value in fractional volts (V).
115    #[inline]
116    pub fn volts(&self) -> f64 {
117        self.raw as f64 / 1_000_000_f64
118    }
119
120    /// Returns the voltage value in fractional kilovolts (kV).
121    #[inline]
122    pub fn kilo_volts(&self) -> f64 {
123        self.raw as f64 / 1_000_000_000_f64
124    }
125
126    /// Returns whether the voltage value is zero volts (0V).
127    #[inline]
128    pub const fn is_zero(&self) -> bool {
129        self.raw == 0
130    }
131
132    /// Returns whether the voltage value is positive.
133    ///
134    /// This returns `true` if the voltage value is greater than or equal to zero volts (0V).
135    #[inline]
136    pub const fn is_positive(&self) -> bool {
137        self.raw >= 0
138    }
139
140    /// Returns whether the voltage value is negative.
141    ///
142    /// This returns `true` if the voltage value is less than zero volts (0V).
143    #[inline]
144    pub const fn is_negative(&self) -> bool {
145        self.raw < 0
146    }
147
148    /// Returns the absolute value of the voltage value.
149    #[inline]
150    pub const fn abs(&self) -> Self {
151        Self::from_micro_volts(self.raw.abs())
152    }
153
154    /// Inverts the voltage value from positive to negative or negative to positive.
155    #[inline]
156    pub const fn invert(&self) -> Self {
157        Self::from_micro_volts(self.raw * -1)
158    }
159
160    /// Returns a `Voltage` value of zero volts (0V).
161    #[inline]
162    pub const fn zero() -> Self {
163        Self::from_micro_volts(0)
164    }
165}
166
167impl PartialEq for Voltage {
168    #[inline]
169    fn eq(&self, other: &Self) -> bool {
170        self.raw == other.raw
171    }
172}
173
174impl Eq for Voltage {}
175
176impl PartialOrd for Voltage {
177    #[inline]
178    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
179        self.raw.partial_cmp(&other.raw)
180    }
181}
182
183impl Ord for Voltage {
184    #[inline]
185    fn cmp(&self, other: &Self) -> cmp::Ordering {
186        self.raw.cmp(&other.raw)
187    }
188}
189
190impl ops::Add for Voltage {
191    type Output = Self;
192
193    /// Adds two `Voltage` values together, returning a new `Voltage` value.
194    #[inline]
195    fn add(self, other: Self) -> Self {
196        self.raw
197            .checked_add(other.raw)
198            .map(Self::from_micro_volts)
199            .expect("Overflow when adding voltage values")
200    }
201}
202
203impl ops::Sub for Voltage {
204    type Output = Self;
205
206    /// Subtracts the `Voltage` value from another, returning a new `Voltage` value.
207    #[inline]
208    fn sub(self, other: Self) -> Self {
209        self.raw
210            .checked_sub(other.raw)
211            .map(Self::from_micro_volts)
212            .expect("Overflow when subtracting voltage values")
213    }
214}
215
216macro_rules! impl_mul_for_integer {
217    ($i: ty) => {
218        impl ops::Mul<$i> for Voltage {
219            type Output = Self;
220
221            /// Multiplies the `Voltage` value by an integer value, returning a new `Voltage` value.
222            #[inline]
223            fn mul(self, other: $i) -> Self {
224                self.raw
225                    .checked_mul(other as i64)
226                    .map(Self::from_micro_volts)
227                    .expect("Overflow when multiplying voltage value")
228            }
229        }
230    };
231}
232
233impl_mul_for_integer!(u8);
234impl_mul_for_integer!(u16);
235impl_mul_for_integer!(u32);
236impl_mul_for_integer!(u64);
237impl_mul_for_integer!(i8);
238impl_mul_for_integer!(i16);
239impl_mul_for_integer!(i32);
240impl_mul_for_integer!(i64);
241
242impl ops::Mul<f32> for Voltage {
243    type Output = Self;
244
245    /// Multiplies the `Voltage` value by a floating-point value, returning a new `Voltage` value.
246    #[inline]
247    fn mul(self, scale_factor: f32) -> Self {
248        self * scale_factor as f64
249    }
250}
251
252impl ops::Mul<f64> for Voltage {
253    type Output = Self;
254
255    /// Multiplies the `Voltage` value by a floating-point value, returning a new `Voltage` value.
256    #[inline]
257    fn mul(self, scale_factor: f64) -> Self {
258        let result = match scale_factor {
259            _ if scale_factor.is_infinite() => {
260                panic!("Cannot multiply voltage value by infinity")
261            }
262            _ if scale_factor.is_nan() => panic!("Cannot multiply voltage value by NaN"),
263            _ => self.raw as f64 * scale_factor,
264        };
265
266        Self::from_micro_volts(result as i64)
267    }
268}
269
270macro_rules! impl_div_for_integer {
271    ($i: ty) => {
272        impl ops::Div<$i> for Voltage {
273            type Output = Self;
274
275            /// Divides the `Voltage` value by an integer value, returning a new `Voltage` value.
276            #[inline]
277            fn div(self, divisor: $i) -> Self {
278                if divisor == 0 {
279                    panic!("Cannot divide voltage value by zero");
280                }
281                self.raw
282                    .checked_div(divisor as i64)
283                    .map(Self::from_micro_volts)
284                    .expect("Overflow when dividing voltage value")
285            }
286        }
287    };
288}
289
290impl_div_for_integer!(u8);
291impl_div_for_integer!(u16);
292impl_div_for_integer!(u32);
293impl_div_for_integer!(u64);
294impl_div_for_integer!(i8);
295impl_div_for_integer!(i16);
296impl_div_for_integer!(i32);
297impl_div_for_integer!(i64);
298
299impl ops::Div<f32> for Voltage {
300    type Output = Self;
301
302    /// Divides the `Voltage` value by a floating-point value, returning a new `Voltage` value.
303    #[inline]
304    fn div(self, divisor: f32) -> Self {
305        self / divisor as f64
306    }
307}
308
309impl ops::Div<f64> for Voltage {
310    type Output = Self;
311
312    /// Divides the `Voltage` value by a floating-point value, returning a new `Voltage` value.
313    #[inline]
314    fn div(self, divisor: f64) -> Self {
315        let result = match divisor {
316            _ if divisor == 0f64 => panic!("Cannot divide voltage value by zero"),
317            _ if divisor.is_infinite() => {
318                panic!("Cannot divide voltage value by infinity")
319            }
320            _ if divisor.is_nan() => panic!("Cannot divide voltage value by NaN"),
321            _ => (self.raw as f64) / divisor,
322        };
323
324        Self::from_micro_volts(result as i64)
325    }
326}
327
328/// Extension trait for simple short-hands for creating `Voltage` values from integer values.
329pub trait FromInteger {
330    /// Creates a new `Voltage` from a number of whole microvolts (μV).
331    fn micro_volts(self) -> Voltage;
332
333    /// Creates a new `Voltage` from a number of whole millivolts (mV).
334    fn milli_volts(self) -> Voltage;
335
336    /// Creates a new `Voltage` from a number of whole volts (V).
337    fn volts(self) -> Voltage;
338
339    /// Creates a new `Voltage` from a number of whole kilovolts (kV).
340    fn kilo_volts(self) -> Voltage;
341}
342
343macro_rules! impl_voltage_from_integer {
344    ($i: ty) => {
345        impl FromInteger for $i {
346            #[inline]
347            fn micro_volts(self) -> Voltage {
348                Voltage::from_micro_volts(self as i64)
349            }
350
351            #[inline]
352            fn milli_volts(self) -> Voltage {
353                let microvolts = (self as i64)
354                    .checked_mul(1_000)
355                    .expect("Overflow when converting millivolts to microvolts");
356                Voltage::from_micro_volts(microvolts)
357            }
358
359            #[inline]
360            fn volts(self) -> Voltage {
361                let microvolts = (self as i64)
362                    .checked_mul(1_000_000)
363                    .expect("Overflow when converting volts to microvolts");
364                Voltage::from_micro_volts(microvolts)
365            }
366
367            #[inline]
368            fn kilo_volts(self) -> Voltage {
369                let microvolts = (self as i64)
370                    .checked_mul(1_000_000_000)
371                    .expect("Overflow when converting kilovolts to microvolts");
372                Voltage::from_micro_volts(microvolts)
373            }
374        }
375    };
376}
377
378impl_voltage_from_integer!(u8);
379impl_voltage_from_integer!(u16);
380impl_voltage_from_integer!(u32);
381impl_voltage_from_integer!(u64);
382impl_voltage_from_integer!(i8);
383impl_voltage_from_integer!(i16);
384impl_voltage_from_integer!(i32);
385impl_voltage_from_integer!(i64);
386
387/// Extension trait for simple short-hands for creating `Voltage` values from floating-point values.
388pub trait FromFloat {
389    /// Creates a new `Voltage` from a number of fractional microvolts (μV).
390    ///
391    /// The fractional part is rounded down to the nearest whole microvolt (μV).
392    fn micro_volts(self) -> Voltage;
393
394    /// Creates a new `Voltage` from a number of fractional millivolts (mV).
395    ///
396    /// The fractional part is rounded down to the nearest whole microvolt (μV).
397    fn milli_volts(self) -> Voltage;
398
399    /// Creates a new `Voltage` from a number of fractional volts (V).
400    ///
401    /// The fractional part is rounded down to the nearest whole microvolt (μV).
402    fn volts(self) -> Voltage;
403
404    /// Creates a new `Voltage` from a number of fractional kilovolts (kV).
405    ///
406    /// The fractional part is rounded down to the nearest whole microvolt (μV).
407    fn kilo_volts(self) -> Voltage;
408}
409
410macro_rules! impl_voltage_from_float {
411    ($f: ty) => {
412        impl FromFloat for $f {
413            #[inline]
414            fn micro_volts(self) -> Voltage {
415                Voltage::from_micro_volts(self as i64)
416            }
417
418            #[inline]
419            fn milli_volts(self) -> Voltage {
420                let microvolts = (self as f64) * 1_000f64;
421                Voltage::from_micro_volts(microvolts as i64)
422            }
423
424            #[inline]
425            fn volts(self) -> Voltage {
426                let microvolts = (self as f64) * 1_000_000f64;
427                Voltage::from_micro_volts(microvolts as i64)
428            }
429
430            #[inline]
431            fn kilo_volts(self) -> Voltage {
432                let microvolts = (self as f64) * 1_000_000_000f64;
433                Voltage::from_micro_volts(microvolts as i64)
434            }
435        }
436    };
437}
438
439impl_voltage_from_float!(f32);
440impl_voltage_from_float!(f64);
441
442impl fmt::Display for Voltage {
443    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
444        let sign = if self.raw < 0 { "-" } else { "" };
445        let microvolts = self.micro_volts().abs() as f64;
446        let kilovolts = microvolts / 1_000_000_000f64;
447        let volts = microvolts / 1_000_000f64;
448        let millivolts = microvolts / 1_000f64;
449
450        if kilovolts >= 1f64 {
451            write!(f, "{sign}{kilovolts:.2} kV")
452        } else if volts >= 1f64 {
453            write!(f, "{sign}{volts:.2} V")
454        } else if millivolts > 0f64 {
455            write!(f, "{sign}{millivolts:.2} mV")
456        } else {
457            write!(f, "{sign}{microvolts:.2} μV")
458        }
459    }
460}