1717NUM_TYPES = {'int' : int , 'float' : float } # type: dict[str, Type]
1818# Assumes "IEEE" in OpenLCB CDI Standard means IEEE 754-2008:
1919FLOAT_MAXIMUMS = {2 : 65504.0 , 4 : 3.40e38 , 8 : 1.80e308 } # type: dict[int, float] # noqa: E501
20+ # Float minimums (https://en.wikipedia.org/wiki/IEEE_754):
21+ # 16-bit smallest normal 6.10×10−5 subnormal 5.96×10−8
22+ # 32-bit smallest normal 1.18×10−38 subnormal 1.40×10−45
23+ # 64-bit smallest normal 2.23×10−308 subnormal 4.94×10−324
24+ F_MIN_BITS = {
25+ 2 : [0 ] * 16 ,
26+ 4 : [0 ] * 32 ,
27+ 8 : [0 ] * 64 ,
28+ }
29+
30+ # Set bits for the most negative finite number
31+ for size , bits in F_MIN_BITS .items ():
32+ bits [0 ] = 1 # Sign bit = 1 (negative)
33+ if size == 2 : # binary16: 1 sign + 5 exp + 10 mant
34+ for i in range (1 , 6 ): bits [i ] = 1 # exp = 11110
35+ bits [5 ] = 0 # ← important: clear LSB of exponent
36+ for i in range (6 , 16 ): bits [i ] = 1 # mantissa all 1s
37+ elif size == 4 : # binary32: 1 sign + 8 exp + 23 mant
38+ for i in range (1 , 9 ): bits [i ] = 1 # exp = 11111110
39+ bits [8 ] = 0 # ← clear LSB of exponent
40+ for i in range (9 , 32 ): bits [i ] = 1
41+ else : # binary64: 1 sign + 11 exp + 52 mant
42+ for i in range (1 , 12 ): bits [i ] = 1 # exp = 111...1110
43+ bits [11 ] = 0 # ← clear LSB of exponent
44+ for i in range (12 , 64 ): bits [i ] = 1
45+
46+ FLOAT_MINIMUMS = {} # F_MIN_DATA = {}
47+
48+ for k , bits in F_MIN_BITS .items ():
49+ # Create traceable binary string (e.g. "0b000...001")
50+ bit_str = "0b" + "" .join (map (str , bits ))
51+ # Convert to integer then to bytes
52+ value = int (bit_str , 2 )
53+ data_bytes = value .to_bytes (k , 'big' )
54+ fmt = {2 : ">e" , 4 : ">f" , 8 : ">d" }[k ]
55+ # F_MIN_DATA[k] = data_bytes
56+ FLOAT_MINIMUMS [k ] = struct .unpack (fmt , data_bytes )[0 ]
57+ # print(f"binary{k*8:2d} bits: {bit_str}")
58+ # print(f"binary{k*8:2d} value: {F_MIN_DATA[k]:.20e}\n")
59+ # results:
60+ # -65504.0
61+ # -3.4028234663852886e+38
62+ # -1.7976931348623157e+308
63+
2064UNSIGNED_INT_MAXIMUMS = { # type: dict[int, int]
2165 1 : 0xFF , 2 : 0xFFFF , 4 : 0xFFFF_FFFF , 8 : 0xFFFF_FFFF_FFFF_FFFF }
2266SIGNED_INT_MINIMUMS = {}
@@ -89,7 +133,8 @@ class CDIVar:
89133
90134 def __init__ (self , className , _min = None , _max = None ,
91135 _size = None , _default = None , assert_range = False ,
92- _no_min = False , _no_max = False , _default_data = None ):
136+ _no_min = False , _no_max = False , _default_data = None ,
137+ signed = None ):
93138 self .data = None # type: bytes|None
94139 self .min = _min # type: CDIVar|None
95140 self .max = _max # type: CDIVar|None
@@ -101,8 +146,6 @@ def __init__(self, className, _min=None, _max=None,
101146 f"Expected { list (CLASSNAME_TYPES .keys ())} got { className } "
102147 if _default is not None :
103148 assert isinstance (_default , CDIVar )
104- if className in NUM_TYPES :
105- _default .assertNumberFormat ()
106149 assert _default_data is None , \
107150 "Can only set _default or _default_data"
108151 elif _default_data is not None :
@@ -113,9 +156,18 @@ def __init__(self, className, _min=None, _max=None,
113156 _no_max = True , _no_min = True ) # prevent recursion
114157 _default .data = _default_data
115158
159+ if _default is not None :
160+ if className in NUM_TYPES :
161+ _default .assertNumberFormat ()
162+ if _default < 0 :
163+ if signed is None :
164+ signed = True
165+
116166 self .name = None # type: str|None
117167 self .className = className # type: str
118- self .signed = False # type: bool
168+ if signed is None :
169+ signed = False
170+ self .signed = signed # type: bool
119171 assert isinstance (_no_min , bool )
120172 assert isinstance (_no_max , bool )
121173 self ._no_min = _no_min
@@ -140,9 +192,26 @@ def __init__(self, className, _min=None, _max=None,
140192 raise AssertionError (error )
141193 else :
142194 logger .error (error )
143- elif not _no_min :
144- self .min = CDIVar (className , _size = _size ,
145- _no_min = True , _no_max = True ) # prevent inf recurs
195+ elif (className in NUM_TYPES ) and not _no_min :
196+ # self.min = CDIVar(className, _size=_size,
197+ # _no_min=True, _no_max=True) # prevent inf recurs
198+ # Set minimum based on size,
199+ # as per Configuration Description Information Standard.
200+ assert _size is not None
201+ if className == "int" :
202+ if signed :
203+ # self.min.setInt(SIGNED_INT_MINIMUMS[_size])
204+ self .min = CDIVar .fromInt (SIGNED_INT_MINIMUMS [_size ],
205+ _size )
206+ else :
207+ # self.min.setInt(0)
208+ self .min = CDIVar .fromInt (0 , _size )
209+ elif className == "float" :
210+ # self.min.setFloat(FLOAT_MINIMUMS[_size])
211+ self .min = CDIVar .fromFloat (FLOAT_MINIMUMS [_size ], _size )
212+ else :
213+ raise NotImplementedError (f"no default minimum { className } " )
214+
146215 if _max is not None :
147216 assert isinstance (_max , CDIVar )
148217 _max .assertNumberFormat ()
@@ -342,9 +411,11 @@ def cmp_any(cls, self: 'CDIVar', other: Union['CDIVar', float, int],
342411 @classmethod
343412 def fromNumber (cls , value : Union [int , float ],
344413 className : str , _size : int ) -> 'CDIVar' :
345- var = CDIVar (className , _size = _size )
414+ var = CDIVar (className , _size = _size , _no_min = True )
415+ # ^ _no_min prevents infinite recursion generating min
346416 if value < 0 :
347417 var .signed = True
418+ var .min = None # remove default (0)
348419 if className == "int" :
349420 assert isinstance (value , int )
350421 var .setInt (value )
@@ -484,6 +555,7 @@ def intToData(self, value: int) -> bytes:
484555 try :
485556 return struct .pack (self .packFormat (), value )
486557 except Exception as ex :
558+ logger .error ("" )
487559 logger .error (formatted_ex (ex ))
488560 logger .error (
489561 f"Tried to set a(n) { self .subtype ()} CDIVar"
0 commit comments