Basic Usage

This guide covers the fundamental operations with I24 and U24 types.

Creating Instances

From Python int

The most common way to create I24 and U24 values is from Python integers:

from i24 import I24, U24

# Create signed 24-bit integer
signed = I24(1000)
print(signed)  # I24(1000)

# Create unsigned 24-bit integer
unsigned = U24(50000)
print(unsigned)  # U24(50000)

Value Range Validation

The constructors automatically validate that values are within the valid range:

from i24 import I24, U24

# Valid ranges:
# I24: -8,388,608 to 8,388,607
# U24: 0 to 16,777,215

try:
    # This will raise ValueError (out of range)
    invalid = I24(10_000_000)
except ValueError as e:
    print(f"Error: {e}")

try:
    # This will raise ValueError (negative for unsigned)
    invalid = U24(-100)
except ValueError as e:
    print(f"Error: {e}")

From Bytes

Create values from byte sequences with configurable byte order:

from i24 import I24, U24

# Little-endian bytes
bytes_le = bytes([0x00, 0x10, 0x00])
value = I24.from_bytes(bytes_le, byteorder='little')
print(value.to_int())  # 4096

# Big-endian bytes
bytes_be = bytes([0x00, 0x10, 0x00])
value = I24.from_bytes(bytes_be, byteorder='big')
print(value.to_int())  # 4096

# Native byte order (platform-dependent)
value = I24.from_bytes(bytes([0x01, 0x02, 0x03]), byteorder='native')

Converting to Python Types

To int

Convert 24-bit integers to Python int:

from i24 import I24, U24

signed = I24(-1000)
unsigned = U24(2000)

# Explicit conversion
int_val = signed.to_int()
print(int_val)  # -1000

# Using __int__() magic method
int_val = int(unsigned)
print(int_val)  # 2000

To Bytes

Convert to byte sequences:

from i24 import I24

value = I24(0x123456)

# Convert to little-endian bytes
le_bytes = value.to_bytes(byteorder='little')
print(le_bytes)  # [86, 52, 18]

# Convert to big-endian bytes
be_bytes = value.to_bytes(byteorder='big')
print(be_bytes)  # [18, 52, 86]

# Native byte order
native_bytes = value.to_bytes(byteorder='native')

Comparisons

I24 and U24 support all standard comparison operators:

from i24 import I24, U24

a = I24(100)
b = I24(200)
c = I24(100)

# Equality
print(a == c)  # True
print(a == b)  # False
print(a != b)  # True

# Ordering
print(a < b)   # True
print(a <= c)  # True
print(b > a)   # True
print(b >= a)  # True

# Works with U24 too
x = U24(1000)
y = U24(2000)
print(x < y)   # True

String Representations

from i24 import I24, U24

value = I24(12345)

# str() - returns just the number
print(str(value))   # "12345"

# repr() - returns the full representation
print(repr(value))  # "I24(12345)"

# Works in f-strings
print(f"Value: {value}")  # "Value: 12345"

Hashing

I24 and U24 are hashable and can be used in sets and as dictionary keys:

from i24 import I24, U24

# Use in sets
values = {I24(100), I24(200), I24(100)}
print(len(values))  # 2 (duplicates removed)

# Use as dictionary keys
mapping = {
    I24(1): "one",
    I24(2): "two",
    U24(100): "hundred"
}
print(mapping[I24(1)])  # "one"

Immutability

Both I24 and U24 are frozen (immutable) types. Once created, their values cannot be changed:

from i24 import I24

value = I24(100)

# This would raise an error if you tried:
# value.value = 200  # AttributeError: can't set attribute

# Instead, create a new instance
new_value = I24(200)

Class Attributes

Access minimum and maximum values:

from i24 import I24, U24

# I24 range
print(I24.min_value)  # I24(-8388608)
print(I24.max_value)  # I24(8388607)

# U24 range
print(U24.min_value)  # U24(0)
print(U24.max_value)  # U24(16777215)

Type Checking

from i24 import I24, U24

value = I24(100)

# Check type
print(isinstance(value, I24))  # True
print(isinstance(value, U24))  # False

# Type hints work properly
def process_signed(val: I24) -> int:
    return val.to_int() * 2

result = process_signed(value)
print(result)  # 200