avr_int24/
lib.rs

1// -*- coding: utf-8 -*-
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright (C) 2025 Michael Büsch <m@bues.ch>
4
5//! # 24 bit integer arithmetic for AVR
6//!
7//! This library provides a 24-bit signed integer type, `Int24`, for Rust.
8//! It is designed for use on AVR microcontrollers.
9//!
10//! No operation from this crate ever panics.
11//!
12//! The operations don't overflow or underflow.
13//! Numeric limits are handled by saturating the result instead.
14//! The only exception is the left shift operation, which does not saturate.
15//!
16//! Here are some example uses:
17//!
18//! ```
19//! use avr_int24::Int24;
20//!
21//! let a = Int24::from_i16(30_000);
22//! let b = Int24::from_i16(10_000);
23//!
24//! // Addition
25//! let c = a + b;
26//! assert_eq!(c.to_i32(), 40_000);
27//!
28//! // Subtraction
29//! let c = b - a;
30//! assert_eq!(c.to_i32(), -20_000);
31//!
32//! // Multiplication
33//! let c = a * Int24::from_i16(-10);
34//! assert_eq!(c.to_i32(), -300_000);
35//!
36//! // Division
37//! let c = a / b;
38//! assert_eq!(c.to_i32(), 3);
39//!
40//! // Negation
41//! let c = -a;
42//! assert_eq!(c.to_i32(), -30_000);
43//!
44//! // Arithmetic right shift
45//! let c = a >> 2;
46//! assert_eq!(c.to_i32(), 7_500);
47//!
48//! // Left shift
49//! let c = a << 2;
50//! assert_eq!(c.to_i32(), 120_000);
51//!
52//! // Saturation
53//! let c = a * b;
54//! assert_eq!(c.to_i32(), 0x7F_FFFF);
55//! let c = a * -b;
56//! assert_eq!(c.to_i32(), -0x80_0000);
57//! ```
58
59#![cfg_attr(not(test), no_std)]
60#![cfg_attr(target_arch = "avr", feature(asm_experimental_arch))]
61
62pub use crate::raw::Int24Raw;
63use crate::raw::{
64    abs24, add24,
65    conv::{
66        cast_i24raw_to_i8, cast_i24raw_to_i16, i8_to_i24raw, i16_to_i24raw, i24raw_to_i8_sat,
67        i24raw_to_i16_sat, i24raw_to_i32, i32_to_i24raw_sat,
68    },
69    div24, eq24, ge24, mul24, neg24, raw_zero, shl24, shl24_by8, shl24_by8_div24, shl24_by16,
70    shr24, shr24_by8, shr24_by16, sub24,
71};
72
73#[cfg(not(target_arch = "avr"))]
74mod asm_generic;
75#[cfg(not(target_arch = "avr"))]
76use asm_generic as asm;
77
78#[cfg(target_arch = "avr")]
79mod asm_avr;
80#[cfg(target_arch = "avr")]
81use asm_avr as asm;
82
83#[cfg(any(feature = "__internal_test__", test))]
84pub mod unit_tests;
85
86mod raw;
87
88/// Shorthand for [Int24].
89pub type I24 = Int24;
90
91/// 24 bit signed integer.
92#[derive(Copy, Clone, Eq, PartialEq, Debug)]
93#[repr(transparent)]
94pub struct Int24(Int24Raw);
95
96#[allow(clippy::should_implement_trait)]
97impl Int24 {
98    /// Construct a new zero [Int24].
99    pub const fn zero() -> Self {
100        Self(raw_zero())
101    }
102
103    /// Construct a new zero [Int24].
104    pub const fn new() -> Self {
105        Self::zero()
106    }
107
108    /// Construct a new [Int24] from a little endian raw tuple.
109    pub const fn from_raw(v: Int24Raw) -> Self {
110        Self(v)
111    }
112
113    /// Construct a new [Int24] from raw little endian bytes.
114    pub const fn from_le_bytes(bytes: [u8; 3]) -> Self {
115        Self::from_raw((bytes[0], bytes[1], bytes[2]))
116    }
117
118    /// Construct a new [Int24] from a signed 8 bit integer.
119    pub const fn from_i8(v: i8) -> Self {
120        Self::from_raw(i8_to_i24raw(v))
121    }
122
123    /// Construct a new [Int24] from a signed 16 bit integer.
124    pub const fn from_i16(v: i16) -> Self {
125        Self::from_raw(i16_to_i24raw(v))
126    }
127
128    /// Construct and saturate a new [Int24] from a signed 32 bit integer.
129    pub const fn from_i32(v: i32) -> Self {
130        Self(i32_to_i24raw_sat(v))
131    }
132
133    /// Convert this [Int24] to little endian bytes.
134    pub const fn to_le_bytes(self) -> [u8; 3] {
135        [self.0.0, self.0.1, self.0.2]
136    }
137
138    /// Convert and saturate this [Int24] to a signed 8 bit integer.
139    pub const fn to_i8(self) -> i8 {
140        i24raw_to_i8_sat(self.0)
141    }
142
143    /// Convert and saturate this [Int24] to a signed 16 bit integer.
144    pub const fn to_i16(self) -> i16 {
145        i24raw_to_i16_sat(self.0)
146    }
147
148    /// Convert this [Int24] to a signed 32 bit integer.
149    pub const fn to_i32(self) -> i32 {
150        i24raw_to_i32(self.0)
151    }
152
153    /// Cast this [Int24] to a signed 8 bit integer *without* *saturation*.
154    pub const fn cast_to_i8(self) -> i8 {
155        cast_i24raw_to_i8(self.0)
156    }
157
158    /// Cast this [Int24] to a signed 16 bit integer *without* *saturation*.
159    pub const fn cast_to_i16(self) -> i16 {
160        cast_i24raw_to_i16(self.0)
161    }
162
163    /// Add and saturate two [Int24].
164    #[inline(never)]
165    pub fn add(self, other: Self) -> Self {
166        Self::from_raw(add24(self.0, other.0))
167    }
168
169    /// Add and saturate two [Int24].
170    /// This is the `const` variant.
171    ///
172    /// Only call this from `const` context.
173    /// From non-`const` context call [Int24::add] instead to get optimized code.
174    pub const fn const_add(self, other: Self) -> Self {
175        Self::from_i32(self.to_i32() + other.to_i32())
176    }
177
178    /// Subtract and saturate two [Int24].
179    #[inline(never)]
180    pub fn sub(self, other: Self) -> Self {
181        Self::from_raw(sub24(self.0, other.0))
182    }
183
184    /// Subtract and saturate two [Int24].
185    /// This is the `const` variant.
186    ///
187    /// Only call this from `const` context.
188    /// From non-`const` context call [Int24::sub] instead to get optimized code.
189    pub const fn const_sub(self, other: Self) -> Self {
190        Self::from_i32(self.to_i32() - other.to_i32())
191    }
192
193    /// Multiply and saturate two [Int24].
194    #[inline(never)]
195    pub fn mul(self, other: Self) -> Self {
196        Self::from_raw(mul24(self.0, other.0))
197    }
198
199    /// Multiply and saturate two [Int24].
200    /// This is the `const` variant.
201    ///
202    /// Only call this from `const` context.
203    /// From non-`const` context call [Int24::mul] instead to get optimized code.
204    pub const fn const_mul(self, other: Self) -> Self {
205        Self::from_i32(self.to_i32() * other.to_i32())
206    }
207
208    /// Divide and saturate two [Int24].
209    #[inline(never)]
210    pub fn div(self, other: Self) -> Self {
211        Self::from_raw(div24(self.0, other.0))
212    }
213
214    /// Divide and saturate two [Int24].
215    /// This is the `const` variant.
216    ///
217    /// Only call this from `const` context.
218    /// From non-`const` context call [Int24::div] instead to get optimized code.
219    pub const fn const_div(self, other: Self) -> Self {
220        Self::from_i32(self.to_i32() / other.to_i32())
221    }
222
223    /// Left shift `self` by 8 bits and then divide the shifted value by `other`.
224    /// The result is saturated to signed 24 bit.
225    /// The intermediate left shift by 8 bits is *not* saturated.
226    ///
227    /// The shifted intermediate value is kept as 32 bits,
228    /// so it doesn't have to be saturated.
229    #[inline(never)]
230    pub fn shl8div(self, other: Self) -> Self {
231        Self::from_raw(shl24_by8_div24(self.0, other.0))
232    }
233
234    /// Left shift `self` by 8 bits and then divide the shifted value by `other`.
235    /// The result is saturated to signed 24 bit.
236    /// The intermediate left shift by 8 bits is *not* saturated.
237    /// This is the `const` variant.
238    ///
239    /// Only call this from `const` context.
240    /// From non-`const` context call [Int24::shl8div] instead to get optimized code.
241    pub fn const_shl8div(self, other: Self) -> Self {
242        Self::from_i32((self.to_i32() << 8) / other.to_i32())
243    }
244
245    /// Two's complement negate and saturate `self`.
246    #[inline(never)]
247    pub fn neg(self) -> Self {
248        Self(neg24(self.0))
249    }
250
251    /// Two's complement negate and saturate `self`.
252    /// This is the `const` variant.
253    ///
254    /// Only call this from `const` context.
255    /// From non-`const` context call [Int24::neg] instead to get optimized code.
256    pub const fn const_neg(self) -> Self {
257        Self::from_i32(-self.to_i32())
258    }
259
260    /// Get the saturated absolute value of `self`.
261    #[inline(never)]
262    pub fn abs(self) -> Self {
263        Self(abs24(self.0))
264    }
265
266    /// Get the saturated absolute value of `self`.
267    /// This is the `const` variant.
268    ///
269    /// Only call this from `const` context.
270    /// From non-`const` context call [Int24::abs] instead to get optimized code.
271    pub const fn const_abs(self) -> Self {
272        if self.to_i32() < 0 {
273            self.const_neg()
274        } else {
275            self
276        }
277    }
278
279    /// Left shift `self` by 8 bits.
280    ///
281    /// This operation does not saturate the result.
282    ///
283    /// This operation is equivalent to calling `shl(8)`, but it is much faster.
284    pub const fn shl8(self) -> Self {
285        Self(shl24_by8(self.0))
286    }
287
288    /// Left shift `self` by 16 bits.
289    ///
290    /// This operation does not saturate the result.
291    ///
292    /// This operation is equivalent to calling `shl(16)`, but it is much faster.
293    pub const fn shl16(self) -> Self {
294        Self(shl24_by16(self.0))
295    }
296
297    /// Left shift `self` by `count` number of bits.
298    ///
299    /// This operation does not saturate the result.
300    #[inline(never)]
301    pub fn shl(self, count: u8) -> Self {
302        Self(shl24(self.0, count))
303    }
304
305    /// Left shift `self` by `count` number of bits.
306    /// This operation does not saturate the result.
307    /// This is the `const` variant.
308    ///
309    /// Only call this from `const` context.
310    /// From non-`const` context call [Int24::shl] instead to get optimized code.
311    pub const fn const_shl(self, count: u8) -> Self {
312        Self::from_i32(self.to_i32() << count)
313    }
314
315    /// Arithmetically right shift `self` by 8 bits.
316    ///
317    /// This operation is equivalent to calling `shr(8)`, but it is much faster.
318    pub const fn shr8(self) -> Self {
319        Self(shr24_by8(self.0))
320    }
321
322    /// Arithmetically right shift `self` by 16 bits.
323    ///
324    /// This operation is equivalent to calling `shr(16)`, but it is much faster.
325    pub const fn shr16(self) -> Self {
326        Self(shr24_by16(self.0))
327    }
328
329    /// Arithmetically right shift `self` by `count` number of bits.
330    #[inline(never)]
331    pub fn shr(self, count: u8) -> Self {
332        Self(shr24(self.0, count))
333    }
334
335    /// Arithmetically right shift `self` by `count` number of bits.
336    /// This is the `const` variant.
337    ///
338    /// Only call this from `const` context.
339    /// From non-`const` context call [Int24::shr] instead to get optimized code.
340    pub const fn const_shr(self, count: u8) -> Self {
341        Self::from_i32(self.to_i32() >> count)
342    }
343
344    /// Compare `self` to `other` and return the result as [core::cmp::Ordering].
345    #[inline(never)]
346    pub fn cmp(self, other: Self) -> core::cmp::Ordering {
347        if eq24(self.0, other.0) {
348            core::cmp::Ordering::Equal
349        } else if ge24(self.0, other.0) {
350            core::cmp::Ordering::Greater
351        } else {
352            core::cmp::Ordering::Less
353        }
354    }
355
356    /// Compare `self` to `other` and return the result as [core::cmp::Ordering].
357    /// This is the `const` variant.
358    ///
359    /// Only call this from `const` context.
360    /// From non-`const` context call [Int24::cmp] instead to get optimized code.
361    pub const fn const_cmp(self, other: Self) -> core::cmp::Ordering {
362        if self.to_i32() == other.to_i32() {
363            core::cmp::Ordering::Equal
364        } else if self.to_i32() >= other.to_i32() {
365            core::cmp::Ordering::Greater
366        } else {
367            core::cmp::Ordering::Less
368        }
369    }
370}
371
372impl Default for Int24 {
373    fn default() -> Self {
374        Self::new()
375    }
376}
377
378impl core::cmp::Ord for Int24 {
379    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
380        Self::cmp(*self, *other)
381    }
382}
383
384impl core::cmp::PartialOrd for Int24 {
385    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
386        Some(self.cmp(other))
387    }
388}
389
390impl core::ops::Add for Int24 {
391    type Output = Self;
392
393    fn add(self, other: Self) -> Self {
394        Self::add(self, other)
395    }
396}
397
398impl core::ops::AddAssign for Int24 {
399    fn add_assign(&mut self, other: Self) {
400        self.0 = (*self + other).0;
401    }
402}
403
404impl core::ops::Sub for Int24 {
405    type Output = Self;
406
407    fn sub(self, other: Self) -> Self {
408        Self::sub(self, other)
409    }
410}
411
412impl core::ops::SubAssign for Int24 {
413    fn sub_assign(&mut self, other: Self) {
414        self.0 = (*self - other).0;
415    }
416}
417
418impl core::ops::Mul for Int24 {
419    type Output = Self;
420
421    fn mul(self, other: Self) -> Self {
422        Self::mul(self, other)
423    }
424}
425
426impl core::ops::MulAssign for Int24 {
427    fn mul_assign(&mut self, other: Self) {
428        self.0 = (*self * other).0;
429    }
430}
431
432impl core::ops::Div for Int24 {
433    type Output = Self;
434
435    fn div(self, other: Self) -> Self {
436        Self::div(self, other)
437    }
438}
439
440impl core::ops::DivAssign for Int24 {
441    fn div_assign(&mut self, other: Self) {
442        self.0 = (*self / other).0;
443    }
444}
445
446impl core::ops::Neg for Int24 {
447    type Output = Self;
448
449    fn neg(self) -> Self {
450        Self::neg(self)
451    }
452}
453
454impl core::ops::Shl<u8> for Int24 {
455    type Output = Self;
456
457    fn shl(self, other: u8) -> Self {
458        Self::shl(self, other)
459    }
460}
461
462impl core::ops::ShlAssign<u8> for Int24 {
463    fn shl_assign(&mut self, other: u8) {
464        self.0 = (*self << other).0;
465    }
466}
467
468impl core::ops::Shr<u8> for Int24 {
469    type Output = Self;
470
471    fn shr(self, other: u8) -> Self {
472        Self::shr(self, other)
473    }
474}
475
476impl core::ops::ShrAssign<u8> for Int24 {
477    fn shr_assign(&mut self, other: u8) {
478        self.0 = (*self >> other).0;
479    }
480}
481
482#[cfg(test)]
483mod test {
484    use crate::unit_tests;
485
486    struct TestRunner {}
487
488    impl unit_tests::TestOps for TestRunner {
489        fn print(&self, text: &str) {
490            print!("{text}");
491        }
492
493        fn print_num(&self, value: u32) {
494            print!("{value}");
495        }
496
497        fn begin(&self, name: &str) {
498            println!("Begin: {name}");
499        }
500
501        fn assert(&self, line: u16, ok: bool) {
502            if ok {
503                println!("line {line}: Ok");
504            } else {
505                panic!("line {line}: FAILED");
506            }
507        }
508    }
509
510    #[test]
511    fn test_int24() {
512        let t = TestRunner {};
513        unit_tests::run_tests(&t);
514    }
515}
516
517// vim: ts=4 sw=4 expandtab