Arithmetic Operations
I24 and U24 support comprehensive arithmetic operations with multiple overflow handling strategies.
Basic Arithmetic
Standard Operators
The standard arithmetic operators work as expected:
from i24 import I24, U24
a = I24(1000)
b = I24(500)
# Addition
result = a + b
print(result.to_int()) # 1500
# Subtraction
result = a - b
print(result.to_int()) # 500
# Multiplication
result = a * b
print(result.to_int()) # 500000
# Division
result = a / b
print(result) # 2.0 (returns float)
# Floor division
result = a // b
print(result.to_int()) # 2
# Modulo
result = a % b
print(result.to_int()) # 0
Overflow Behavior
Standard operators raise OverflowError when results exceed the valid range:
from i24 import I24
a = I24(8_000_000)
b = I24(500_000)
try:
# This will overflow
result = a + b
except OverflowError as e:
print(f"Overflow: {e}")
Division by Zero
Division operations raise ZeroDivisionError:
from i24 import I24
a = I24(1000)
zero = I24(0)
try:
result = a / zero
except ZeroDivisionError as e:
print(f"Error: {e}")
Checked Arithmetic
Checked arithmetic methods return None on overflow instead of raising exceptions:
Addition
from i24 import I24
a = I24(8_000_000)
b = I24(500_000)
# Safe addition
result = a.checked_add(b)
if result is None:
print("Addition would overflow")
else:
print(f"Result: {result.to_int()}")
# Successful addition
c = I24(100)
d = I24(200)
result = c.checked_add(d)
print(result.to_int()) # 300
Subtraction
from i24 import I24, U24
# Signed subtraction
a = I24(-8_000_000)
b = I24(500_000)
result = a.checked_sub(b)
if result is None:
print("Subtraction would underflow")
# Unsigned subtraction
x = U24(100)
y = U24(200)
result = x.checked_sub(y)
if result is None:
print("Cannot subtract larger from smaller for unsigned")
Multiplication
from i24 import I24
a = I24(10_000)
b = I24(1_000)
result = a.checked_mul(b)
if result is None:
print("Multiplication would overflow")
else:
print(f"Result: {result.to_int()}")
Division
from i24 import I24
a = I24(1000)
b = I24(0)
result = a.checked_div(b)
if result is None:
print("Division by zero")
else:
print(f"Result: {result.to_int()}")
Wrapping Arithmetic
Wrapping operations allow overflow/underflow by wrapping around the valid range:
from i24 import I24, U24
# I24 wrapping addition
a = I24(8_388_607) # I24::MAX
b = I24(10)
result = a.wrapping_add(b)
print(result.to_int()) # Wraps to negative
# U24 wrapping subtraction
x = U24(5)
y = U24(10)
result = x.wrapping_sub(y)
print(result.to_int()) # Wraps to large positive
# Wrapping multiplication
c = I24(10_000)
d = I24(2_000)
result = c.wrapping_mul(d)
print(result.to_int()) # Wrapped result
Saturating Arithmetic
Saturating operations clamp results to the valid range (min/max values):
from i24 import I24, U24
# I24 saturating addition
a = I24(8_388_600)
b = I24(1_000)
result = a.saturating_add(b)
print(result.to_int()) # 8388607 (I24::MAX)
# I24 saturating subtraction
c = I24(-8_388_600)
d = I24(1_000)
result = c.saturating_sub(d)
print(result.to_int()) # -8388608 (I24::MIN)
# U24 saturating operations
x = U24(16_777_210)
y = U24(100)
result = x.saturating_add(y)
print(result.to_int()) # 16777215 (U24::MAX)
# Saturating multiplication
m = I24(100_000)
n = I24(200)
result = m.saturating_mul(n)
print(result.to_int()) # 8388607 (I24::MAX)
Unary Operations
Negation
from i24 import I24
a = I24(1000)
neg_a = -a
print(neg_a.to_int()) # -1000
# Negating I24::MIN raises OverflowError
min_val = I24.min_value
try:
neg_min = -min_val
except OverflowError as e:
print(f"Cannot negate MIN: {e}")
Absolute Value
from i24 import I24
a = I24(-1000)
abs_a = abs(a)
print(abs_a.to_int()) # 1000
# abs(I24::MIN) raises OverflowError
min_val = I24.min_value
try:
abs_min = abs(min_val)
except OverflowError as e:
print(f"Cannot take abs of MIN: {e}")
Positive
from i24 import I24, U24
a = I24(1000)
pos_a = +a # Returns self
print(pos_a.to_int()) # 1000
Best Practices
Use checked arithmetic for untrusted input:
def safe_add(a: I24, b: I24) -> I24 | None: return a.checked_add(b)
Use saturating arithmetic for clamped values:
# Ensure audio sample never exceeds valid range def clamp_sample(value: I24, adjustment: I24) -> I24: return value.saturating_add(adjustment)
Use wrapping arithmetic when modular behavior is desired:
# Counter that wraps around def increment_counter(counter: U24) -> U24: return counter.wrapping_add(U24(1))
Handle exceptions appropriately:
try: result = a + b except OverflowError: # Log error, use fallback, etc. result = I24.max_value