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