"""
This module provides signed integer types for binary I/O operations.
It implements various signed integer types (8-bit, 16-bit, 32-bit, 64-bit) that can be used
to read from and write to binary files or streams. The module also provides platform-dependent
type aliases that match C language integer types.
The main class is :class:`CSignedIntType`, which handles the conversion between binary data
and signed integer values. It supports reading and writing signed integers of different sizes
while handling two's complement representation.
Example::
>>> import io
>>> from hbutils.binary import c_int32
>>>
>>> # Reading signed integers
>>> with io.BytesIO(b'\\xde\\xad\\xbe\\xef') as file:
... value = c_int32.read(file)
... print(value)
-272716322
>>> # Writing signed integers
>>> with io.BytesIO() as file:
... c_int32.write(file, -272716322)
... print(file.getvalue())
b'\\xde\\xad\\xbe\\xef'
"""
import ctypes
from typing import Dict, BinaryIO, List
from .base import CRangedIntType
from .uint import CUnsignedIntType
__all__ = [
'CSignedIntType',
'c_int8', 'c_int16', 'c_int32', 'c_int64',
'c_byte', 'c_short', 'c_int', 'c_long', 'c_longlong',
]
[docs]
class CSignedIntType(CRangedIntType):
"""
Signed integer type for binary I/O operations.
This class provides functionality to read and write signed integers of a specific size
to and from binary streams. It handles two's complement representation internally by
using an unsigned integer type for the actual I/O operations.
:param size: Size of the integer type in bytes.
:type size: int
Example::
>>> import io
>>> from hbutils.binary import CSignedIntType
>>>
>>> # Create a 2-byte signed integer type
>>> int16_type = CSignedIntType(2)
>>>
>>> # Read a signed integer
>>> with io.BytesIO(b'\\xde\\xad') as file:
... value = int16_type.read(file)
... print(value)
-21026
>>> # Write a signed integer
>>> with io.BytesIO() as file:
... int16_type.write(file, -21026)
... print(file.getvalue())
b'\\xde\\xad'
"""
[docs]
def __init__(self, size: int):
"""
Initialize a signed integer type with the specified size.
:param size: Size of the integer type in bytes.
:type size: int
The constructor sets up the internal unsigned integer type for I/O operations
and calculates the valid range for signed integers of this size.
"""
self.__size = size
self._unit = CUnsignedIntType(size)
self.__half = 1 << (8 * self.__size - 1)
CRangedIntType.__init__(
self, self.__size,
self._unit.minimum - self.__half,
self._unit.maximum - self.__half
)
[docs]
def read(self, file: BinaryIO) -> int:
"""
Read a signed integer value from a binary stream.
:param file: Binary file or stream to read from. ``io.BytesIO`` is supported as well.
:type file: BinaryIO
:return: The signed integer value read from the stream.
:rtype: int
This method reads the unsigned representation and converts it to a signed integer
using two's complement representation.
Example::
>>> import io
>>> from hbutils.binary import c_int16
>>>
>>> with io.BytesIO(b'\\xde\\xad') as file:
... value = c_int16.read(file)
... print(value)
-21026
"""
uval = self._unit.read(file)
if uval < self.__half:
return uval
else:
return uval - (self.__half << 1)
[docs]
def write(self, file: BinaryIO, val: int):
"""
Write a signed integer value to a binary stream.
:param file: Binary file or stream to write to. ``io.BytesIO`` is supported as well.
:type file: BinaryIO
:param val: Signed integer value to write.
:type val: int
:raises TypeError: If the value is not an integer.
:raises ValueError: If the value is outside the valid range for this integer type.
This method converts the signed integer to its unsigned two's complement representation
before writing to the stream.
Example::
>>> import io
>>> from hbutils.binary import c_int16
>>>
>>> with io.BytesIO() as file:
... c_int16.write(file, -21026)
... print(file.getvalue())
b'\\xde\\xad'
"""
if not isinstance(val, int):
raise TypeError(f'Int value expected, but {repr(val)} found.')
elif not (self.minimum <= val <= self.maximum):
raise ValueError(f'Signed int value within '
f'[{self.minimum}, {self.maximum}] expected, but {repr(val)} found.')
fval = val if val >= 0 else val + (self.__half << 1)
self._unit.write(file, fval)
c_int8 = CSignedIntType(ctypes.sizeof(ctypes.c_int8))
"""
Reading and writing signed integer with 8-bits.
This type represents a signed 8-bit integer with a range of -128 to 127.
Examples::
>>> import io
>>> from hbutils.binary import c_int8
>>>
>>> with io.BytesIO(b'\\xde\\xad\\xbe\\xef') as file:
... print(c_int8.read(file))
... print(c_int8.read(file))
... print(c_int8.read(file))
... print(c_int8.read(file))
-34
-83
-66
-17
>>> with io.BytesIO() as file:
... c_int8.write(file, -34)
... c_int8.write(file, -83)
... c_int8.write(file, -66)
... c_int8.write(file, -17)
... print(file.getvalue())
b'\\xde\\xad\\xbe\\xef'
"""
c_int16 = CSignedIntType(ctypes.sizeof(ctypes.c_int16))
"""
Reading and writing signed integer with 16-bits.
This type represents a signed 16-bit integer with a range of -32768 to 32767.
Examples::
>>> import io
>>> from hbutils.binary import c_int16
>>>
>>> with io.BytesIO(b'\\xde\\xad\\xbe\\xef\\x12\\x34\\x56\\xf7') as file:
... print(c_int16.read(file))
... print(c_int16.read(file))
... print(c_int16.read(file))
... print(c_int16.read(file))
-21026
-4162
13330
-2218
>>> with io.BytesIO() as file:
... c_int16.write(file, -21026)
... c_int16.write(file, -4162)
... c_int16.write(file, 13330)
... c_int16.write(file, -2218)
... print(file.getvalue())
b'\\xde\\xad\\xbe\\xef\\x124V\\xf7'
"""
c_int32 = CSignedIntType(ctypes.sizeof(ctypes.c_int32))
"""
Reading and writing signed integer with 32-bits.
This type represents a signed 32-bit integer with a range of -2147483648 to 2147483647.
Examples::
>>> import io
>>> from hbutils.binary import c_int32
>>>
>>> with io.BytesIO(b'\\xde\\xad\\xbe\\xef\\x12\\x34\\x56\\xf7') as file:
... print(c_int32.read(file))
... print(c_int32.read(file))
-272716322
-145345518
>>> with io.BytesIO() as file:
... c_int32.write(file, -272716322)
... c_int32.write(file, -145345518)
... print(file.getvalue())
b'\\xde\\xad\\xbe\\xef\\x124V\\xf7'
"""
c_int64 = CSignedIntType(ctypes.sizeof(ctypes.c_int64))
"""
Reading and writing signed integer with 64-bits.
This type represents a signed 64-bit integer with a range of -9223372036854775808 to 9223372036854775807.
Examples::
>>> import io
>>> from hbutils.binary import c_int64
>>>
>>> with io.BytesIO(b'\\xde\\xad\\xbe\\xef\\x12\\x34\\x56\\xf7') as file:
... print(c_int64.read(file))
-624254242407928354
>>> with io.BytesIO() as file:
... c_int64.write(file, -624254242407928354)
... print(file.getvalue())
b'\\xde\\xad\\xbe\\xef\\x124V\\xf7'
"""
_EXIST_TYPES: List[CSignedIntType] = [
c_int8,
c_int16,
c_int32,
c_int64,
]
_SIZE_TO_INT_TYPE: Dict[int, CSignedIntType] = {
item.size: item
for item in _EXIST_TYPES
}
def _get_from_raw(tp) -> CSignedIntType:
"""
Get the corresponding CSignedIntType instance for a ctypes integer type.
:param tp: A ctypes integer type (e.g., ctypes.c_int, ctypes.c_long).
:return: The corresponding CSignedIntType instance.
:rtype: CSignedIntType
This internal function maps ctypes integer types to their corresponding
CSignedIntType instances based on size.
"""
return _SIZE_TO_INT_TYPE[ctypes.sizeof(tp)]
c_byte = _get_from_raw(ctypes.c_byte)
"""
Alias for :data:`c_int8`.
This type represents a signed byte, equivalent to a signed 8-bit integer.
"""
c_short = _get_from_raw(ctypes.c_short)
"""
Alias for :data:`c_int16`.
This type represents a short integer, equivalent to a signed 16-bit integer.
"""
c_int = _get_from_raw(ctypes.c_int)
"""
Alias for :data:`c_int32` (in 64-bit OS).
.. note::
Size of :data:`c_int` is the same as that in C language, which mainly depends on CPU and OS.
On most modern 64-bit systems, this is a 32-bit signed integer.
"""
c_long = _get_from_raw(ctypes.c_long)
"""
Alias for :data:`c_int64` (in 64-bit OS).
.. note::
Size of :data:`c_long` is the same as that in C language, which mainly depends on CPU and OS.
On 64-bit Unix-like systems, this is typically a 64-bit signed integer.
On Windows, it may be 32-bit even on 64-bit systems.
"""
c_longlong = _get_from_raw(ctypes.c_longlong)
"""
Alias for :data:`c_int64` (in 64-bit OS).
.. note::
Size of :data:`c_longlong` is the same as that in C language, which mainly depends on CPU and OS.
This is typically a 64-bit signed integer on all modern platforms.
"""