ferrunitas 0.4.0

A type-safe unit conversion library with compile-time dimensional analysis
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
//! Unit trait & macros for defining base, derived, compound and prefixed units.
//!
//! This module provides the foundation for the unit system in Ferrunitas. Units are
//! zero-sized marker types that carry compile-time information about their associated
//! quantity, conversion factor, and display symbol. The primary interface is the
//! [`unit!`] macro, which can create four different types of units:

use core::fmt::Debug;

use crate::Real;
use crate::model::{measure::Measure, quantity::QuantityMarker};

/// Core trait implemented by every concrete unit type.
///
/// The `Unit` trait is the foundation of the type system, providing the essential
/// information needed for dimensional analysis and unit conversions. Each unit is
/// a zero-sized marker type that carries compile-time information about its
/// associated quantity, conversion factor, and display symbol.
///
/// Units are typically created using the [`crate::unit!`] macro rather than implementing
/// this trait manually. The trait provides the scaffolding for type-safe arithmetic
/// and conversions between different units of the same dimension.
///
/// # Associated Types and Constants
///
/// - **`Quantity`**: The dimensional quantity this unit measures (e.g., `Length`, `Mass`)
/// - **`FACTOR`**: Conversion factor to canonical SI base units
/// - **`ABBREV`**: Short symbol for display (e.g., "m", "kg", "ft")
///
/// # Examples
///
/// ```
/// use ferrunitas::system::*;
/// use ferrunitas::{Measure, Unit};
///
/// // Units provide compile-time information
/// assert_eq!(Metre::FACTOR, 1.0);        // Base SI unit
/// assert_eq!(Metre::ABBREV, "m");
///
/// assert_eq!(Foot::FACTOR, 0.3048);      // Conversion to metres
/// assert_eq!(Foot::ABBREV, "ft");
///
/// assert_eq!(Kilometre::FACTOR, 1000.0); // 1000 metres per kilometre
/// assert_eq!(Kilometre::ABBREV, "km");
///
/// // Temperature scales are a bit special due to offsets
/// assert_eq!(DegreeCelsius::FACTOR, 1.0);
/// assert_eq!(DegreeCelsius::OFFSET, 273.15);
/// assert_eq!(DegreeCelsius::ABBREV, "°C");
///
/// // Create measures using the unit's new() method
/// let distance = Metre::new(100.0);
/// let height = Foot::new(6.0);
/// let long_distance = Kilometre::new(5.0);
///
/// // Units can be converted between compatible types via the Measure struct
/// let distance_in_feet: Measure<Foot> = distance.convert();
/// println!("100 metres = {:.2}", distance_in_feet);
/// ```
///
/// # Unit Arithmetic
///
/// Units participate in dimensional arithmetic through their associated measures:
///
/// ```
/// use ferrunitas::{system::*, Unit};
///
/// let length = Metre::new(10.0);
/// let time = Second::new(2.0);
///
/// // Division creates a new quantity with proper dimensions
/// let velocity: Velocity = length / time;  // 10 m ÷ 2 s = 5 m/s
///
/// let speed_kmh = velocity.as_measure::<KilometrePerHour>();
/// println!("Velocity: {}", speed_kmh);  // 18 km/h
/// ```
pub trait Unit: Debug + Clone + Copy + PartialEq + PartialOrd {
    /// The associated quantity type for this unit.
    type Quantity: QuantityMarker;
    /// The scaling factor to canonical base units.
    const FACTOR: Real;
    /// The offset to canonical base units (for affine units like Celsius)
    const OFFSET: Real;
    /// The abbreviation for this unit.
    const ABBREV: &'static str;

    /// Create a new measure from a raw value
    ///
    /// This is the primary constructor for creating [`Measure`] instances.
    /// It takes any value that can be converted to `crate::Real` and wraps it in
    /// a measure with this unit type.
    ///
    /// # Examples
    ///
    /// ```
    /// use ferrunitas::{system::*, Unit};
    ///
    /// // Create measures with different numeric types
    /// let distance1 = Metre::new(100.0f32);   // f32
    /// let distance2 = Metre::new(100i16);     // i16
    /// let distance3 = Metre::new(100u16);     // u16
    ///
    /// // All create equivalent measures
    /// assert_eq!(distance1.value(), 100.0);
    /// assert_eq!(distance2.value(), 100.0);
    /// assert_eq!(distance3.value(), 100.0);
    ///
    /// // Works with any unit type
    /// let mass = Kilogram::new(5.5);
    /// let time = Second::new(10i16);
    /// let force = Newton::new(50.0);
    /// ```
    fn new(value: impl Into<Real>) -> Measure<Self> {
        Measure::new(value.into())
    }
}

// ============================================
// MACROS
// ============================================

/// Macro to define a new unit
///
/// This is the primary macro for creating unit types in the Ferrunitas library.
/// It supports four different patterns for unit definition, each suited to different
/// use cases. All generated units implement the [`Unit`] trait and can be used
/// with [`Measure`] for type-safe calculations.
///
/// # Unit Types
///
/// ## 1. Base Units (`base:`)
///
/// Define fundamental units for a quantity, typically with a factor of 1.0 for SI base units.
///
/// ```
/// use ferrunitas::{unit, quantity, typenum_consts::*};
///
/// quantity!(Length: M Z0, L P1, T Z0, I Z0, Th Z0, N Z0, J Z0);
/// quantity!(Mass: M P1, L Z0, T Z0, I Z0, Th Z0, N Z0, J Z0);
/// quantity!(Time: M Z0, L Z0, T P1, I Z0, Th Z0, N Z0, J Z0);
///
/// // SI base units
/// unit!(base: Metre, "m", Length; prefixable);
/// unit!(base: Gram, "g", Mass; prefixable, factor = 0.001); // Adjusts for kg base (anomaly in SI system)
///
/// // Non-prefixable base unit
/// unit!(base: Second, "s", Time);
/// ```
///
/// ## 2. Derived Units (`derived:`)
///
/// Create units as scaled versions of existing units using a simple multiplication factor.
///
/// ```
/// use ferrunitas::unit;
/// use ferrunitas::system::*;
///
/// // Imperial length units
/// unit!(derived: Inch, "in", (2.54, Centimetre));
/// unit!(derived: Foot, "ft", (12, Inch));
/// unit!(derived: Yard, "yd", (3, Foot));
/// unit!(derived: Mile, "mi", (1760, Yard));
///
/// // Mass units
/// unit!(derived: Pound, "lb", (453.592, Gram));
/// unit!(derived: Ounce, "oz", (28.3495, Gram));
///
/// // Some derived units can be prefixable
/// unit!(derived: Tonne, "t", (1000, Kilogram); prefixable);
///
/// // Temperatures use offsets, however this is rather unusual. Providing an offset will limit the
/// // unit from being used with prefixes, and it's strongly discouraged to use these as compound units,
/// // as the offsets are lost (so only differences in temperature are meaningful then).
/// unit!(derived: DegreeCelsius, "°C", (1.0, 273.15, Kelvin));
/// ```
///
/// ## 3. Compound Units (`compound:`)
///
/// Define units by combining multiple existing units with exponents, like m/s² or kg⋅m/s².
/// Avoids hard coding conversion factors if the underlying factors are already present.
/// E.g., since we know one kilometer equals 1000 meters and one hour equals 3600 seconds,
/// we can express km/h as a compound unit instead of defining it as 0.277778 m/s.
///
/// ```
/// use ferrunitas::{unit, typenum_consts::*};
/// use ferrunitas::system::*;
///
/// // Velocity: length/time
/// unit!(compound: MetrePerSecond, "m/s", [(Metre, P1), (Second, N1)]);
/// unit!(compound: KilometrePerHour, "km/h", [(Kilometre, P1), (Hour, N1)]);
///
/// // Acceleration: length/time²
/// unit!(compound: MetrePerSecondSquared, "m/s²", [(Metre, P1), (Second, N2)]);
///
/// // Force: mass × acceleration
/// unit!(compound: Newton, "N", [(Kilogram, P1), (Metre, P1), (Second, N2)]);
///
/// // With scalar factors
/// unit!(compound: YardsPerMinute, "yd/min", [(3, Foot, P1), (60, Second, N1)]);
///
/// // With quantity tagging (requires `quantity_tags` feature)
/// unit!(compound: BitPerSecond, "bit/s", [(Bit, P1), (Second, N1)]; marked DataRate);
/// unit!(compound: RadianPerSecond, "rad/s", [(Radian, P1), (Second, N1)]; marked AngularVelocity);
/// ```
///
/// ## 4. Prefixed Units (`prefix:`)
///
/// Create prefixed versions of existing units automatically.
///
/// ```
/// use ferrunitas::{unit, prefix};
/// use ferrunitas::system::*;
///
/// prefix!(Kilo, 1000, "k");
/// prefix!(Centi, 0.01, "c");
/// prefix!(Milli, 0.001, "m");
///
/// // These create Kilometre, Centimetre, Millimetre automatically
/// unit!(prefix: Kilometre, Kilo, Metre);
/// unit!(prefix: Centimetre, Centi, Metre);
/// unit!(prefix: Millimetre, Milli, Metre);
/// ```
///
/// # Options
///
/// - **`prefixable`**: Allows the unit to be used with prefixes
/// - **`factor = value`**: Override the conversion factor for base units
/// - **`marked QuantityType`**: (compound units only) Associate with a specific tagged quantity type (requires `quantity_tags` feature)
///
/// # Generated Code
///
/// Each `unit!` invocation creates:
/// - A zero-sized struct representing the unit
/// - Implementation of the [`Unit`] trait with proper constants
/// - Implementation of `Display` for the unit symbol
/// - Optional implementation of trait to mark it as compatible with prefixes if specified
///
/// # Examples
///
/// ## Complete Example
///
/// ```
/// use ferrunitas::{unit, prefix, quantity, typenum_consts::*, Measure, Unit};
///
/// // Define the quantity
/// quantity!(Length: M Z0, L P1, T Z0, I Z0, Th Z0, N Z0, J Z0);
///
/// // Define base unit
/// unit!(base: Metre, "m", Length; prefixable);
///
/// // Define prefixes
/// prefix!(Kilo, 1000, "k");
/// prefix!(Centi, 0.01, "c");
///
/// // Create prefixed units
/// unit!(prefix: Kilometre, Kilo, Metre);
/// unit!(prefix: Centimetre, Centi, Metre);
///
/// // Define derived units
/// unit!(derived: Foot, "ft", (0.3048, Metre));
/// unit!(derived: Inch, "in", (2.54, Centimetre));
///
/// // Usage
/// let distance = Metre::new(100.0);
/// let km_distance: Measure<Kilometre> = distance.convert();
/// let ft_distance: Measure<Foot> = distance.convert();
///
/// println!("Distance: {} = {} = {}", distance, km_distance, ft_distance);
/// ```
#[macro_export]
macro_rules! unit {
    // Base units (prefixable or not)
    (base: $unit_name:ident, $abbrev:literal, $quantity:ty $(; $($optionals:tt)*)? ) => {
        unit!(base_internal: $unit_name, $abbrev, $quantity, 1.0; $($($optionals)*)?);
    };


    (base_internal: $unit_name:ident, $abbrev:literal, $quantity:ty, $factor:expr; prefixable $(, $($optionals:tt)*)?) => {
        unit!(base_internal: $unit_name, $abbrev, $quantity, $factor; $($($optionals)*)?);

        impl $crate::__model::Prefixable for $unit_name {}
    };

    (base_internal: $unit_name:ident, $abbrev:literal, $quantity:ty, $factor:expr; factor = $new_factor:expr $(, $($optionals:tt)*)?) => {
        unit!(base_internal: $unit_name, $abbrev, $quantity, $new_factor; $($($optionals)*)?);
    };

    (base_internal: $unit_name:ident, $abbrev:literal, $quantity:ty, $factor:expr;) => {
        $crate::__unit!($unit_name, $quantity, $factor, $abbrev);
    };

    // Derived units based on other unit
    (derived: $unit_name:ident,  $abbrev:literal, ($factor:expr, $base_unit:ty); prefixable) => {
        unit!(derived: $unit_name, $abbrev, ($factor, $base_unit));

        impl $crate::__model::Prefixable for $unit_name {}
    };

    (derived: $unit_name:ident,  $abbrev:literal, ($factor:expr, $base_unit:ty)) => {
        unit!(derived: $unit_name, $abbrev, ($factor, 0.0, $base_unit));
    };

    (derived: $unit_name:ident, $abbrev:literal, ($factor:expr, $offset:expr, $base_unit:ty)) => {
        $crate::__unit!(
            $unit_name,
            <$base_unit as $crate::__model::Unit>::Quantity,
            ($factor as $crate::Real) * <$base_unit as $crate::__model::Unit>::FACTOR,
            <$base_unit as $crate::__model::Unit>::OFFSET + (<$base_unit as $crate::__model::Unit>::FACTOR * $offset),
            $abbrev
        );
    };

    // Compound unit
    (compound: $unit_name:ident, $abbrev:literal, [$($components:tt),+] $(; $($optionals:tt)*)? ) => {
        unit!(compound_internal: $unit_name, $abbrev, (), [$($components),+]; $($($optionals)*)?);
    };


    (compound_internal: $unit_name:ident, $abbrev:literal, $name_tag:ty, [$($components:tt),+]; prefixable $(, $($optionals:tt)*)?) => {
        unit!(compound_internal: $unit_name, $abbrev, $name_tag, [$($components),+]; $($($optionals)*)?);

        impl $crate::__model::Prefixable for $unit_name {}
    };

    (compound_internal: $unit_name:ident, $abbrev:literal, $name_tag:ty, [$($components:tt),+]; marked $quantity_for_tag:ty $(, $($optionals:tt)*)?) => {
        unit!(compound_internal:
            $unit_name,
            $abbrev,
            <$quantity_for_tag as $crate::__model::QuantityMarker>::Tag,
            [$($components),+];
            $($($optionals)*)?
        );
    };

    (compound_internal: $unit_name:ident, $abbrev:literal, $quantity_for_tag:ty, [$($components:tt),+];) => {
        $crate::__compound_unit!(
            $unit_name,
            $abbrev,
            $quantity_for_tag,
            [
                $crate::__model::Quantity<$crate::__model::DimensionZero, ()>,
                1.0 as $crate::Real;
                $($components),+
            ]
        );
    };

    // Prefixed unit
    (prefix: $alias:ident, $prefix:ty, $base_unit:ty) => {
        $crate::__unit!(
            $alias,
            <$base_unit as $crate::__model::Unit>::Quantity,
            <$prefix as $crate::__model::Prefix>::FACTOR * <$base_unit as $crate::__model::Unit>::FACTOR,
            const_format::concatcp!(
                <$prefix as $crate::__model::Prefix>::SYMBOL,
                <$base_unit as $crate::__model::Unit>::ABBREV
            )
        );
    };
}

/// Inner macros
///
/// This module contains implementation details for the [`unit!`] macro.
/// These macros are not intended for direct use and are hidden from the
/// public API. They handle the actual code generation for different unit types.
#[doc(hidden)]
pub mod __inner_unit_macros {
    use crate::Real;

    /// Create a unit struct and impl Unit trait
    #[macro_export]
    #[doc(hidden)]
    macro_rules! __unit {
        // No offset
        ($unit_name:ident, $quantity:ty, $factor:expr, $abbrev:expr) => {
            $crate::__unit!($unit_name, $quantity, $factor, 0.0, $abbrev);
        };

        // Default impl with offset
        ($unit_name:ident, $quantity:ty, $factor:expr, $offset:expr, $abbrev:expr) => {
            #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
            /// Zero-sized marker struct representing a concrete unit.
            ///
            /// Generated by `unit!` macro; implements `Unit` with provided
            /// quantity mapping, factor and abbreviation.
            pub struct $unit_name;

            impl $crate::__model::Unit for $unit_name {
                type Quantity = $quantity;
                const FACTOR: $crate::Real = $factor;
                const OFFSET: $crate::Real = $offset;
                const ABBREV: &'static str = $abbrev;
            }

            impl core::fmt::Display for $unit_name {
                fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
                    write!(f, "{}", <$unit_name as $crate::__model::Unit>::ABBREV)
                }
            }

            impl<U: $crate::__model::Unit> core::ops::Mul<U> for $unit_name
            where
                $quantity: core::ops::Mul<U::Quantity>,
            {
                type Output =
                    <$crate::Measure<$unit_name> as core::ops::Mul<$crate::Measure<U>>>::Output;

                fn mul(self, _rhs: U) -> Self::Output {
                    $crate::Measure::<$unit_name>::new(1.0) * $crate::Measure::<U>::new(1.0)
                }
            }

            #[cfg(feature = "f64")]
            $crate::__unit_times_number!($unit_name, [i32, f64]);

            #[cfg(feature = "f32")]
            $crate::__unit_times_number!($unit_name, [i16, f32]);
        };
    }

    /// Create a compound unit
    #[macro_export]
    #[doc(hidden)]
    macro_rules! __compound_unit {
        // Base case
        ($unit_name:ident, $abbrev:literal, $name_tag:ty, [$quantity:ty, $factor_acc:expr;] ) => {
            $crate::__unit!(
                $unit_name,
                $crate::__model::Quantity<
                    <$quantity as $crate::__model::QuantityMarker>::DimensionVector,
                    $name_tag
                >,
                $factor_acc,
                $abbrev
            );
        };

        // Recursive cases
        ($unit_name:ident, $abbrev:literal, $name_tag:ty, [$quantity:ty, $factor_acc:expr; ($unit:ty, $exp:ty) $(, $components:tt)*] ) => {
            $crate::__compound_unit!(
                $unit_name,
                $abbrev,
                $name_tag,
                [$quantity, $factor_acc; (1.0 as $crate::Real, $unit, $exp) $(, $components)*]
            );
        };

        ($unit_name:ident, $abbrev:literal, $name_tag:ty, [$quantity:ty, $factor_acc:expr; ($scalar:expr, $unit:ty, $exp:ty) $(, $components:tt)*] ) => {
            $crate::__compound_unit!(
                $unit_name,
                $abbrev,
                $name_tag,
                [
                    <$quantity as core::ops::Mul<
                        <<$unit as $crate::__model::Unit>::Quantity as $crate::__model::TypePow<$exp>>::Output
                    >>::Output,
                    $factor_acc * $crate::__model::__inner_unit_macros::__powi_const(
                        ($scalar as $crate::Real) * <$unit as $crate::__model::Unit>::FACTOR,
                        <$exp as typenum::ToInt<i32>>::INT
                    );
                    $($components),*
                ]
            );
        };

        ($unit_name:ident, $abbrev:literal, $quantity_for_tag:ty, [$quantity:ty, $factor_acc:expr; (constant $constant:expr, $unit:ty, $exp:ty) $(, $components:tt)*] ) => {
            $crate::__compound_unit!(
                $unit_name,
                $abbrev,
                $quantity_for_tag,
                [
                    <$quantity as core::ops::Mul<
                        <<$unit as $crate::__model::Unit>::Quantity as $crate::__model::TypePow<$exp>>::Output
                    >>::Output,
                    $factor_acc * $crate::__model::__inner_unit_macros::__powi_const(
                        ($constant).value_const(), <$exp as typenum::ToInt<i32>>::INT
                    );
                    $($components),*
                ]
            );
        };
    }

    /// Implement multiplication of unit by number for QOL instantiation of measures.
    #[doc(hidden)]
    #[macro_export]
    macro_rules! __unit_times_number {
        ($unit:ty, [$($numeric_type:ty),*]) => {
            $(
                impl core::ops::Mul<$unit> for $numeric_type {
                    type Output = $crate::Measure<$unit>;

                    fn mul(self, _rhs: $unit) -> Self::Output {
                        <$unit as $crate::__model::Unit>::new(self)
                    }
                }
            )*
        };
    }

    /// Const fn for integer exponentiation
    ///
    /// Computes `base^exp` at compile time for integer exponents.
    /// Used internally by compound unit macros to calculate conversion factors
    /// when units are raised to various powers.
    ///
    /// This is a compile-time implementation of exponentiation that handles
    /// both positive and negative exponents correctly.
    #[doc(hidden)]
    pub const fn __powi_const(mut base: Real, mut exp: i32) -> Real {
        if exp == 0 {
            return 1.0;
        }
        let neg = exp < 0;
        if neg {
            exp = -exp;
        }
        let mut e = exp as u32;
        let mut acc = 1.0;
        while e != 0 {
            if (e & 1) == 1 {
                acc *= base;
            }
            base *= base;
            e >>= 1;
        }
        if neg { 1.0 / acc } else { acc }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::system::*;

    #[test]
    fn test_unit_new_method() {
        // Test creating measures with different numeric types
        let distance_f32 = Metre::new(100.0f32);
        let distance_i16 = Metre::new(100i16);
        let distance_u16 = Metre::new(100_u16);

        assert_eq!(distance_f32.value(), 100.0);
        assert_eq!(distance_i16.value(), 100.0);
        assert_eq!(distance_u16.value(), 100.0);

        // Test with different unit types
        let mass = Kilogram::new(5.5);
        let time = Second::new(10i16);
        let force = Newton::new(50.0);

        assert_eq!(mass.value(), 5.5);
        assert_eq!(time.value(), 10.0);
        assert_eq!(force.value(), 50.0);

        // Test return type is correct Measure<Unit>
        let _: Measure<Metre> = Metre::new(1.0);
        let _: Measure<Kilogram> = Kilogram::new(1.0);
        let _: Measure<Second> = Second::new(1.0);
    }

    #[test]
    fn test_unit_trait_implementation() {
        // Test that units properly implement the Unit trait
        fn test_unit_trait<U: Unit>(_unit: U) -> (Real, Real, &'static str) {
            (U::FACTOR, U::OFFSET, U::ABBREV)
        }

        let (factor, offset, abbrev) = test_unit_trait(Metre);
        assert_eq!(factor, 1.0);
        assert_eq!(offset, 0.0);
        assert_eq!(abbrev, "m");

        let (factor, offset, abbrev) = test_unit_trait(Kilometre);
        assert_eq!(factor, 1000.0);
        assert_eq!(offset, 0.0);
        assert_eq!(abbrev, "km");

        let (factor, offset, abbrev) = test_unit_trait(DegreeCelsius);
        assert_eq!(factor, 1.0);
        assert_eq!(offset, 273.15);
        assert_eq!(abbrev, "°C");
    }

    #[test]
    fn test_custom_powi_function() {
        use super::__inner_unit_macros::__powi_const;

        // Positive exponents
        let square: Real = __powi_const(2.0, 2);
        let cube: Real = __powi_const(3.0, 3);
        let fourth_power: Real = __powi_const(10.0, 4);
        assert_eq!(square, 4.0); // 2^2 = 4
        assert_eq!(cube, 27.0); // 3^3 = 27
        assert_eq!(fourth_power, 10000.0); // 10^4 = 10000

        // Zero exponent (any base to power 0 equals 1)
        let zero_exp: Real = __powi_const(42.0, 0);
        assert_eq!(zero_exp, 1.0);

        // Negative exponents (reciprocals)
        let neg_square: Real = __powi_const(2.0, -2);
        let neg_cube: Real = __powi_const(4.0, -3);
        assert_eq!(neg_square, 0.25); // 2^-2 = 1/4 = 0.25
        assert_eq!(neg_cube, 1.0 / 64.0); // 4^-3 = 1/64

        // Edge cases
        let one_to_power: Real = __powi_const(1.0, 100);
        let first_power: Real = __powi_const(5.0, 1);
        assert_eq!(one_to_power, 1.0); // 1^100 = 1
        assert_eq!(first_power, 5.0); // 5^1 = 5

        // Used in compound unit factor calculations
        // For example, m/s² would use: metres_factor * __powi_const(seconds_factor, -2)
        let acceleration_factor: Real = 1.0 * __powi_const(1.0, -2); // m * s^-2
        assert_eq!(acceleration_factor, 1.0);
    }
}