B4R Code Snippet Byte to seconds (up to 400 hours)

It's useful for me to use with the MCU sleep.

1776079653452.png


B4X:
'Converts byte (0..255) to seconds (0..1440000)
Sub ByteToSeconds(b As Byte) As ULong
    Dim MAX_SEC As ULong = 1440000    '400 hours max
    If b <= 49 Then
        Return b
    Else If b <= 99 Then
        Return 60 * (b - 49)
    Else If b <= 199 Then
        Return 3600 * (b - 99)
    Else
        'Range 200..255: linear from 100 to 400 hours
        Dim delta As ULong = (b - 200) * 216000
        Dim result As ULong = 360000 + delta / 11
        If result > MAX_SEC Then result = MAX_SEC
        Return result
    End If
End Sub

'Converts seconds (0..1440000) to byte (0..255)
Sub SecondsToByte(sec As ULong) As Byte
    Dim MAX_SEC As ULong = 1440000    '400 hours max
    Dim s As ULong = sec
    If s > MAX_SEC Then s = MAX_SEC
   
    If s <= 49 Then
        Return s
    Else If s <= 3000 Then
        'Round to nearest minute
        Dim b As ULong = (s + 30) / 60 + 49
        If b > 99 Then b = 99
        Return b
    Else If s <= 360000 Then
        'Round to nearest hour
        Dim b As ULong = (s + 1800) / 3600 + 99
        If b > 199 Then b = 199
        Return b
    Else
        'Range: 100..400 hours
        Dim numerator As ULong = (s - 360000) * 55
        Dim b As ULong = 200 + (numerator + 540000) / 1080000   'with rounding
        If b > 255 Then b = 255
        Return b
    End If
End Sub
 
Last edited:

emexes

Expert
Licensed User
Longtime User
Another way would be an 8 bit binary floating point, eg 5 bit exponent and 3 bit mantissa (plus implied leading 1), tuned to best fit the desired range 0..1440000.

No problem to go the other way, just keep shifting right until it is a valid mantissa value (actually, until is one bit larger than a valid mantissa, then add 1 for rounding and shift right one last time, which makes that rounding 1 turn into a rounding 0.5).

B4X:
Sub ByteToSeconds(b As Int) As Int
    If b <= 145 Then Return b
    If b >= 251 Then Return 400 * 3600    '400 hours max
    
    Dim exponent As Byte = Bit.ShiftRight(b, 3) - 14
    Dim mantissa As Byte = 8 + Bit.And(b, 7)

    Return Bit.ShiftLeft(mantissa, exponent)
End Sub

Test with:
For b = 0 To 255
    Log(b & TAB & ByteToSeconds(b))
Next
Log output:
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
10    10
11    11
12    12
13    13
14    14
15    15
16    16
17    17
18    18
19    19
20    20
21    21
22    22
23    23
24    24
25    25
26    26
27    27
28    28
29    29
30    30
31    31
32    32
33    33
34    34
35    35
36    36
37    37
38    38
39    39
40    40
41    41
42    42
43    43
44    44
45    45
46    46
47    47
48    48
49    49
50    50
51    51
52    52
53    53
54    54
55    55
56    56
57    57
58    58
59    59
60    60
61    61
62    62
63    63
64    64
65    65
66    66
67    67
68    68
69    69
70    70
71    71
72    72
73    73
74    74
75    75
76    76
77    77
78    78
79    79
80    80
81    81
82    82
83    83
84    84
85    85
86    86
87    87
88    88
89    89
90    90
91    91
92    92
93    93
94    94
95    95
96    96
97    97
98    98
99    99
100    100
101    101
102    102
103    103
104    104
105    105
106    106
107    107
108    108
109    109
110    110
111    111
112    112
113    113
114    114
115    115
116    116
117    117
118    118
119    119
120    120
121    121
122    122
123    123
124    124
125    125
126    126
127    127
128    128
129    129
130    130
131    131
132    132
133    133
134    134
135    135
136    136
137    137
138    138
139    139
140    140
141    141
142    142
143    143
144    144
145    145
146    160
147    176
148    192
149    208
150    224
151    240
152    256
153    288
154    320
155    352
156    384
157    416
158    448
159    480
160    512
161    576
162    640
163    704
164    768
165    832
166    896
167    960
168    1024
169    1152
170    1280
171    1408
172    1536
173    1664
174    1792
175    1920
176    2048
177    2304
178    2560
179    2816
180    3072
181    3328
182    3584
183    3840
184    4096
185    4608
186    5120
187    5632
188    6144
189    6656
190    7168
191    7680
192    8192
193    9216
194    10240
195    11264
196    12288
197    13312
198    14336
199    15360
200    16384
201    18432
202    20480
203    22528
204    24576
205    26624
206    28672
207    30720
208    32768
209    36864
210    40960
211    45056
212    49152
213    53248
214    57344
215    61440
216    65536
217    73728
218    81920
219    90112
220    98304
221    106496
222    114688
223    122880
224    131072
225    147456
226    163840
227    180224
228    196608
229    212992
230    229376
231    245760
232    262144
233    294912
234    327680
235    360448
236    393216
237    425984
238    458752
239    491520
240    524288
241    589824
242    655360
243    720896
244    786432
245    851968
246    917504
247    983040
248    1048576
249    1179648
250    1310720
251    1440000
252    1440000
253    1440000
254    1440000
255    1440000
 

emexes

Expert
Licensed User
Longtime User
make the reverse sub with good tolerance

Challenge accepted. Give this a burl. Tolerance (resolution?) worst case error should be half mantissa lsb ie 1/16th ≅ 7% cf the nearest-hour method is 50% off for sleep time of 90 minutes, where the fp8 method is less than 0.5% off.

B4X:
Sub SecondsToByte(s As Int) As Int
    If s <=145 Then Return s
    
    Dim exponent As Int = 0
    Dim mantissa As Int = s

    Do While mantissa > 31
        mantissa = Bit.ShiftRight(mantissa, 1)
        exponent = exponent + 1
    Loop
    
    mantissa = mantissa + 1
    
    Do While mantissa > 15
        mantissa = Bit.ShiftRight(mantissa, 1)
        exponent = exponent + 1
    Loop
    
    mantissa = mantissa - 8
    exponent = exponent + 14

    If exponent > 31 Then Return 255    'return maximum byte value if exponent > 5 bits
    
    Return Bit.ShiftLeft(exponent, 3) + (mantissa)   
End Sub
 

peacemaker

Expert
Licensed User
Longtime User
Is Ints not a problem here for Bit that needs UInts ?
Maybe divide and multiply instead ?


B4X:
Sub SecondsToByte(seconds As UInt) As Byte
    If seconds <= 145 Then Return seconds
    If seconds >= 400 * 3600 Then Return 255
 
    Dim exponent As Int = 0
    Dim mantissa As UInt = seconds
 
    'Normalize: bring mantissa into 8..15 range with proper rounding UP
    Do While mantissa > 15
        'Check if we need to round up (odd number before division)
        If mantissa Mod 2 <> 0 Then
            mantissa = mantissa / 2 + 1
        Else
            mantissa = mantissa / 2
        End If
        exponent = exponent + 1
    Loop
 
    'Adjust if mantissa is below 8 (shouldn't happen for seconds > 145, but safety)
    If mantissa < 8 Then
        mantissa = 8
    End If
 
    Dim b As Int = (exponent + 14) * 8 + (mantissa - 8)
 
    'Safety bounds
    If b < 146 Then b = 146
    If b > 250 Then b = 255
 
    Return b
End Sub
 
Last edited:

emexes

Expert
Licensed User
Longtime User
Is Ints not a problem here for Bit that needs UInts ?

Uh, forgot to mention that I've been doing it in B4J because I don't have a working B4J setup. And in B4J bytes are signed eg -128 to 127, so I declared the nominally-8-bit variables to be Ints instead of Bytes in order to bypass the negative wraparound issues.

But B4R bytes are unsigned, and so if you declare the nominally-8-bit variables to be Bytes, the code should still work.

Let me know if it doesn't. It's past my bedtime here and mistakes are possible. 🤪
 
Last edited:

emexes

Expert
Licensed User
Longtime User
Maybe divide and multiply instead ?

The Bit.ShiftRights are the divides and the Bit.ShiftLefts are the multiplies.

On Arduino microcontrollers without hardware divide (and maybe not even hardware multiply) the shift operations will be faster. Although Erel would correctly point out that, in the scheme of things, saving a few hundred clock cycles on infrequent operations often has negligible effect on overall system performance.
 

emexes

Expert
Licensed User
Longtime User
If mantissa Mod 2 <> 0 Then

Lol that makes me wince, because Mod uses same clock cycles as does a divide.

Better to do:

B4X:
If Bit.And(mantissa, 1) <> 0 Then

Also, I'm not sure about doing the rounding every time through the loop. Rounding usually only looks to the next digit, not to all the next digits.

So let's say you were rounding 10100001 to 4 significant bits, doing it each time through the loop would give you:

10100001
1010001
101001
10101
1011

which is clearly not the closest 4 significant bits to the original 1010.0001 binary number.

Or maybe I'm missing something.

Anyway, the way I did it would result in:

10100001
1010000
101000
10100 'the shifting stops here because it is now <= 31 ie 5 bits
10101 'add the rounding 1 (which is effectively 0.5 after the final shift)
1010

but the reason the final shift is a loop rather than just a single shift is because let's say the original number was 01111110

01111110
0111111
011111 'stop because <= 31
100000 'add rounder
10000 'one shift is not enough, because mantissa has to be 4 bits (including leading 1) ie 8 to 15
1000 'saved by the loop
 
Top