Skip to content

Commit 9ff0e22

Browse files
committed
fix: add missing support for c_float and c_double
1 parent c6cfefc commit 9ff0e22

6 files changed

Lines changed: 115 additions & 2 deletions

File tree

docs/basics/types.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ libdestruct provides Python equivalents for common C primitive types. All types
1111
| `c_long` | `long` / `int64_t` | 8 | Yes |
1212
| `c_ulong` | `unsigned long` / `uint64_t` | 8 | No |
1313
| `c_char` | `char` | 1 ||
14+
| `c_float` | `float` | 4 ||
15+
| `c_double` | `double` | 8 ||
1416
| `c_str` | `char[]` | variable ||
1517

1618
## Usage
@@ -56,6 +58,29 @@ x = c_int.from_bytes(b"\x2a\x00\x00\x00")
5658
print(x.value) # 42
5759
```
5860

61+
## Floating-Point Types
62+
63+
`c_float` and `c_double` represent IEEE 754 single-precision (32-bit) and double-precision (64-bit) floating-point numbers.
64+
65+
```python
66+
import struct as pystruct
67+
from libdestruct import c_float, c_double, inflater
68+
69+
# Read a float from bytes
70+
data = pystruct.pack("<f", 3.14)
71+
f = c_float.from_bytes(data)
72+
print(f.value) # 3.140000104904175
73+
print(float(f)) # same — c_float supports the __float__ protocol
74+
75+
# Write a double to mutable memory
76+
memory = bytearray(8)
77+
lib = inflater(memory)
78+
d = lib.inflate(c_double, 0)
79+
d.value = 2.718281828
80+
```
81+
82+
Both types support special values like `NaN`, `inf`, and `-inf`, and respect endianness settings.
83+
5984
## Strings
6085

6186
`c_str` represents a null-terminated C string. It behaves like an array of characters:

libdestruct/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
pass
1313

1414
from libdestruct.backing.resolver import Resolver
15-
from libdestruct.c import c_int, c_long, c_str, c_uint, c_ulong
15+
from libdestruct.c import c_double, c_float, c_int, c_long, c_str, c_uint, c_ulong
1616
from libdestruct.common.array import array, array_of
1717
from libdestruct.common.attributes import offset
1818
from libdestruct.common.bitfield import bitfield_of
@@ -26,6 +26,8 @@
2626
"array",
2727
"array_of",
2828
"bitfield_of",
29+
"c_double",
30+
"c_float",
2931
"c_int",
3032
"c_long",
3133
"c_str",

libdestruct/c/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
# Licensed under the MIT license. See LICENSE file in the project root for details.
55
#
66

7+
from libdestruct.c.c_float_types import c_double, c_float
78
from libdestruct.c.c_integer_types import c_char, c_int, c_long, c_short, c_uchar, c_uint, c_ulong, c_ushort
89
from libdestruct.c.c_str import c_str
910

10-
__all__ = ["c_char", "c_int", "c_long", "c_short", "c_str", "c_uchar", "c_uint", "c_ulong", "c_ushort"]
11+
__all__ = [
12+
"c_char", "c_double", "c_float", "c_int", "c_long", "c_short",
13+
"c_str", "c_uchar", "c_uint", "c_ulong", "c_ushort",
14+
]
1115

1216
import libdestruct.c.base_type_inflater
1317
import libdestruct.c.ctypes_generic_field # noqa: F401

libdestruct/c/base_type_inflater.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
from libdestruct.c.c_float_types import c_double, c_float
910
from libdestruct.c.c_integer_types import _c_integer, c_char, c_int, c_long, c_short, c_uchar, c_uint, c_ulong, c_ushort
1011
from libdestruct.c.c_str import c_str
1112
from libdestruct.common.type_registry import TypeRegistry
@@ -22,4 +23,6 @@
2223
registry.register_mapping(c_uint, c_uint)
2324
registry.register_mapping(c_long, c_long)
2425
registry.register_mapping(c_ulong, c_ulong)
26+
registry.register_mapping(c_float, c_float)
27+
registry.register_mapping(c_double, c_double)
2528
registry.register_mapping(c_str, c_str)

libdestruct/c/c_float_types.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#
2+
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
3+
# Copyright (c) 2026 Roberto Alessandro Bertolini. All rights reserved.
4+
# Licensed under the MIT license. See LICENSE file in the project root for details.
5+
#
6+
7+
from __future__ import annotations
8+
9+
import struct
10+
11+
from libdestruct.common.obj import obj
12+
13+
14+
class c_float(obj):
15+
"""A C float (IEEE 754 single-precision, 32-bit)."""
16+
17+
size: int = 4
18+
"""The size of a float in bytes."""
19+
20+
_frozen_value: float | None = None
21+
"""The frozen value of the float."""
22+
23+
def _format_char(self: c_float) -> str:
24+
return "<f" if self.endianness == "little" else ">f"
25+
26+
def get(self: c_float) -> float:
27+
"""Return the value of the float."""
28+
return struct.unpack(self._format_char(), self.resolver.resolve(self.size, 0))[0]
29+
30+
def _set(self: c_float, value: float) -> None:
31+
"""Set the value of the float."""
32+
self.resolver.modify(self.size, 0, struct.pack(self._format_char(), value))
33+
34+
def to_bytes(self: c_float) -> bytes:
35+
"""Return the serialized representation of the float."""
36+
if self._frozen:
37+
return struct.pack(self._format_char(), self._frozen_value)
38+
return self.resolver.resolve(self.size, 0)
39+
40+
def __float__(self: c_float) -> float:
41+
"""Return the value as a Python float."""
42+
return self.get()
43+
44+
45+
class c_double(obj):
46+
"""A C double (IEEE 754 double-precision, 64-bit)."""
47+
48+
size: int = 8
49+
"""The size of a double in bytes."""
50+
51+
_frozen_value: float | None = None
52+
"""The frozen value of the double."""
53+
54+
def _format_char(self: c_double) -> str:
55+
return "<d" if self.endianness == "little" else ">d"
56+
57+
def get(self: c_double) -> float:
58+
"""Return the value of the double."""
59+
return struct.unpack(self._format_char(), self.resolver.resolve(self.size, 0))[0]
60+
61+
def _set(self: c_double, value: float) -> None:
62+
"""Set the value of the double."""
63+
self.resolver.modify(self.size, 0, struct.pack(self._format_char(), value))
64+
65+
def to_bytes(self: c_double) -> bytes:
66+
"""Return the serialized representation of the double."""
67+
if self._frozen:
68+
return struct.pack(self._format_char(), self._frozen_value)
69+
return self.resolver.resolve(self.size, 0)
70+
71+
def __float__(self: c_double) -> float:
72+
"""Return the value as a Python float."""
73+
return self.get()

libdestruct/c/struct_parser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from pycparser import c_ast, c_parser
1616

17+
from libdestruct.c.c_float_types import c_double, c_float
1718
from libdestruct.c.c_integer_types import c_char, c_int, c_long, c_short, c_uchar, c_uint, c_ulong, c_ushort
1819
from libdestruct.common.array.array_of import array_of
1920
from libdestruct.common.bitfield.bitfield_of import bitfield_of
@@ -253,6 +254,11 @@ def identifier_to_type(identifier: c_ast.IdentifierType) -> type[obj]:
253254

254255
identifier_name = "".join(identifier.names)
255256

257+
# Native float/double types (before ctypes fallback, so we get libdestruct types)
258+
native_float_types = {"float": c_float, "double": c_double}
259+
if identifier_name in native_float_types:
260+
return native_float_types[identifier_name]
261+
256262
ctypes_name = "c_" + identifier_name
257263

258264
if hasattr(ctypes, ctypes_name):

0 commit comments

Comments
 (0)