Add tlv.FloatMember
Adds a FloatMember that encodes LE floats/doubles as specified in A.11.5
This commit is contained in:
parent
6d7ee584e6
commit
4bd35adc5a
2 changed files with 57 additions and 6 deletions
|
|
@ -407,14 +407,46 @@ class IntMember(NumberMember[int, _OPT, _NULLABLE]):
|
|||
nullable: _NULLABLE = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
:param octets: Number of octests to use for encoding.
|
||||
1, 2, 4, 8 are 8, 16, 32, and 64 bits respectively
|
||||
:param optional: Indicates whether the value MAY be omitted from the encoding.
|
||||
Can be used for deprecation.
|
||||
:param nullable: Indicates whether a TLV Null MAY be encoded in place of a value.
|
||||
"""
|
||||
# TODO 7.18.1 mentions other bit lengths (that are not a power of 2) than the TLV Appendix
|
||||
uformat = INT_SIZE[int(math.log2(octets))]
|
||||
# little-endian
|
||||
# < = little-endian
|
||||
self.format = f"<{uformat.lower() if signed else uformat}"
|
||||
super().__init__(
|
||||
tag, _format=self.format, optional=optional, nullable=nullable, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class FloatMember(NumberMember[float, _OPT, _NULLABLE]):
|
||||
def __init__(
|
||||
self,
|
||||
tag,
|
||||
*,
|
||||
octets: Literal[4, 8] = 4,
|
||||
optional: _OPT = False,
|
||||
nullable: _NULLABLE = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
:param octets: Number of octests to use for encoding.
|
||||
4, 8 are single and double precision floats respectively.
|
||||
:param optional: Indicates whether the value MAY be omitted from the encoding.
|
||||
Can be used for deprecation.
|
||||
:param nullable: Indicates whether a TLV Null MAY be encoded in place of a value.
|
||||
"""
|
||||
# < = little-endian
|
||||
self.format = f"<{'f' if octets == 4 else 'd'}"
|
||||
super().__init__(
|
||||
tag, _format=self.format, optional=optional, nullable=nullable, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class BoolMember(Member[bool, _OPT, _NULLABLE]):
|
||||
max_value_length = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -338,11 +338,11 @@ class TestNull:
|
|||
# Double precision floating point negative infinity 0b 00 00 00 00 00 00 f0 ff
|
||||
# (-∞)
|
||||
class FloatSingle(tlv.TLVStructure):
|
||||
f = tlv.NumberMember(None, "f")
|
||||
f = tlv.FloatMember(None)
|
||||
|
||||
|
||||
class FloatDouble(tlv.TLVStructure):
|
||||
f = tlv.NumberMember(None, "d")
|
||||
f = tlv.FloatMember(None, octets=8)
|
||||
|
||||
|
||||
class TestFloatSingle:
|
||||
|
|
@ -398,12 +398,12 @@ class TestFloatSingle:
|
|||
assert s.encode().tobytes() == b"\x0a\x00\x00\x80\xff"
|
||||
|
||||
@given(v=...)
|
||||
def test_roundtrip(self, v: float):
|
||||
s = FloatSingle()
|
||||
def test_roundtrip_double(self, v: float):
|
||||
s = FloatDouble()
|
||||
s.f = v
|
||||
buffer = s.encode().tobytes()
|
||||
|
||||
s2 = FloatSingle(buffer)
|
||||
s2 = FloatDouble(buffer)
|
||||
|
||||
assert (
|
||||
(math.isnan(s.f) and math.isnan(s2.f))
|
||||
|
|
@ -412,6 +412,25 @@ class TestFloatSingle:
|
|||
or math.isclose(s2.f, s.f, rel_tol=1e-7, abs_tol=1e-9)
|
||||
)
|
||||
|
||||
@given(
|
||||
v=st.floats(
|
||||
# encoding to LE float32 raises OverflowError outside these ranges
|
||||
# TODO: should we raise ValueError with a bounds check or encode -inf/inf?
|
||||
min_value=(2**-126),
|
||||
max_value=(2 - 2**-23) * 2**127,
|
||||
),
|
||||
)
|
||||
def test_roundtrip_single(self, v: float):
|
||||
s = FloatSingle()
|
||||
s.f = v
|
||||
buffer = s.encode().tobytes()
|
||||
|
||||
s2 = FloatSingle(buffer)
|
||||
|
||||
assert (math.isnan(s.f) and math.isnan(s2.f)) or math.isclose(
|
||||
s2.f, s.f, rel_tol=1e-7, abs_tol=1e-9
|
||||
)
|
||||
|
||||
|
||||
class TestFloatDouble:
|
||||
def test_precision_float_0_0_decode(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue