DCCL v5
Loading...
Searching...
No Matches
numeric.h
1// Copyright 2009-2025:
2// GobySoft, LLC (2013-)
3// Massachusetts Institute of Technology (2007-2014)
4// Community contributors (see AUTHORS file)
5// File authors:
6// Toby Schneider <toby@gobysoft.org>
7// Cadmus To <cadmus.to+dev@gmail.com>
8//
9//
10// This file is part of the Dynamic Compact Control Language Library
11// ("DCCL").
12//
13// DCCL is free software: you can redistribute it and/or modify
14// it under the terms of the GNU Lesser General Public License as published by
15// the Free Software Foundation, either version 2.1 of the License, or
16// (at your option) any later version.
17//
18// DCCL is distributed in the hope that it will be useful,
19// but WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU Lesser General Public License for more details.
22//
23// You should have received a copy of the GNU Lesser General Public License
24// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
25#ifndef DCCLNumeric20260127H
26#define DCCLNumeric20260127H
27
28#include <bitset>
29#include <cmath>
30#include <iostream>
31#include <limits>
32#include <type_traits>
33#include <cassert>
34
35#include "common.h"
36
37namespace dccl
38{
39
53int32_t decompose_float_format(float val, int16_t& exponent);
54int64_t decompose_float_format(double val, int16_t& exponent);
55
58float compose_float_format(int32_t significand, int16_t exponent);
59double compose_float_format(int64_t significand, int16_t exponent);
60
61template<std::size_t N>
62inline void increment(std::bitset<N> &bits) {
63 for (auto i = 0ul; i < N; ++i) {
64 if ((bits[i] = !bits[i]) == true) {
65 break;
66 }
67 }
68}
69
70template<std::size_t N>
71inline bool is_negative(const std::bitset<N> &bits) {
72 return bits[N-1];
73}
74
75template<std::size_t N>
76inline void negate(std::bitset<N> &bits) {
77 bits.flip();
78 increment(bits);
79}
80
81template<std::size_t N>
82inline std::bitset<N> negated(const std::bitset<N> &bits) {
83 auto ret_val = bits;
84 negate(ret_val);
85 return ret_val;
86}
87
88template<std::size_t N>
89inline void add_to(std::bitset<N> &a, const std::bitset<N> &b) {
90 bool carry_bit = false;
91 for (auto i = 0ul; i < N; ++i) {
92 auto sum = carry_bit + a[i] + b[i];
93 a[i] = sum % 2;
94 carry_bit = sum > 1;
95 }
96}
97
98template<std::size_t N>
99inline std::bitset<N> sum(const std::bitset<N> &a, const std::bitset<N> &b) {
100 auto ret_val = a;
101 add_to(ret_val, b);
102 return ret_val;
103}
104
105template<std::size_t N>
106inline std::bitset<N> difference(const std::bitset<N> &a, const std::bitset<N> &b) {
107 auto ret_val = negated(b);
108 add_to(ret_val, a);
109 return ret_val;
110}
111
112template<std::size_t N>
113inline bool unsigned_geq(const std::bitset<N> &a, const std::bitset<N> &b) {
114 for (auto k = 0ul; k < N; ++k) {
115 const auto i = N - 1 - k;
116 if (a[i] > b[i]) {
117 return true;
118 } else if (b[i] > a[i]) {
119 return false;
120 }
121 }
122 return true;
123}
124
125// Reference: see Long division at https://en.wikipedia.org/wiki/Division_algorithm
126template<std::size_t N>
127inline std::bitset<N> unsigned_quotient(const std::bitset<N> &n, const std::bitset<N> &d) {
128 auto q = std::bitset<N>{0};
129 auto r = std::bitset<N>{0};
130 for (auto k = 0ul; k < N; ++k) {
131 const auto i = N - 1 - k;
132 r <<= 1;
133 r[0] = n[i];
134 if (unsigned_geq(r, d)) {
135 r = difference(r, d);
136 q[i] = true;
137 }
138 }
139
140 // Remainder for rounding
141 r <<= 1;
142 if (unsigned_geq(r, d)) {
143 increment(q);
144 }
145
146 return q;
147}
148
149// Reference: see Other notations at https://en.wikipedia.org/wiki/Multiplication_algorithm
150template<std::size_t N>
151inline std::bitset<2*N> unsigned_product(const std::bitset<N> &a, const std::bitset<N> &b) {
152 auto p = std::bitset<2*N>{0};
153 for (auto b_i = 0ul; b_i < N; ++b_i) {
154 bool carry = false;
155 for (auto a_i = 0ul; a_i < N; ++a_i) {
156 auto sum = p[a_i + b_i] + carry + (a[a_i] * b[b_i]);
157 carry = static_cast<bool>(sum >> 1);
158 p[a_i + b_i] = static_cast<bool>(sum % 2);
159 }
160 p[b_i + N] = carry;
161 }
162
163 return p;
164}
165
166template <std::size_t N>
167inline void rounding_shift_right(std::bitset<N> &bits, uint16_t num_pos) {
168 // Inspect most significant bit for rounding
169 bool need_to_increment = bits[num_pos-1];
170 bits >>= num_pos;
171 if (need_to_increment) {
172 increment(bits);
173 }
174}
175
176// Drops least significant bits until it is represented with the given
177// number of significant bits. The most significant dropped bit is used
178// to round the result.
179// Returns the number of bits dropped.
180template <std::size_t N>
181inline int16_t drop_to_sig_fig(std::bitset<N> &bits, uint16_t target_sig_fig) {
182 if (target_sig_fig == 0) {
183 bits = std::bitset<N>{0};
184 }
185
186 // Find most significant bit
187 auto curr_sig_fig = static_cast<int16_t>(N);
188 while (curr_sig_fig > target_sig_fig) {
189 if (bits[curr_sig_fig-1]) {
190 break;
191 } else {
192 --curr_sig_fig;
193 }
194 }
195
196 auto num_bits_to_drop = curr_sig_fig - target_sig_fig;
197 if (num_bits_to_drop > 0) {
198 rounding_shift_right(bits, num_bits_to_drop);
199 }
200 return num_bits_to_drop;
201}
202
203template <typename T, std::size_t N>
204inline T fill_unsigned(const std::bitset<N> &bits) {
205 auto ret_val = T{};
206
207 constexpr auto num_type_bits = sizeof(T)*8;
208 constexpr auto num_iter = std::min(N, num_type_bits);
209 for (auto k = 0ul; k < num_iter; ++k) {
210 const auto i = num_iter - 1 - k;
211 ret_val <<= 1;
212 ret_val += bits[i];
213 }
214
215 return ret_val;
216}
217
218template <typename Integral, std::enable_if_t<std::is_integral<Integral>::value, bool> = true>
219uint64 encode(Integral value, double min, double res) {
220 Integral wire_value = dccl::quantize(value, res);
221
222 // calculate the encoded value: remove the minimum, scale for the resolution, cast to int.
223 wire_value -= quantize(static_cast<Integral>(min), res);
224 if (res >= 1)
225 wire_value /= res;
226 else
227 wire_value *= (1.0 / res);
228 return static_cast<uint64>(round(wire_value, 0));
229}
230
231template <typename Float, std::enable_if_t<std::is_floating_point<Float>::value, bool> = true>
232uint64 encode(Float value, double min, double res) {
233 // Float cannot compete with the level of detail capable in (u)int32 types
234 // so we need to be careful with the computation. Here we perform computations
235 // on the significand/mantissa and exponent values separately for as long as we
236 // can, then only convert to a the Float right before rounding.
237
238 int16_t val_exp, res_exp, min_exp;
239 auto val_sig_raw = decompose_float_format(value, val_exp);
240 auto res_sig_raw = decompose_float_format(static_cast<Float>(res), res_exp);
241 auto min_sig_raw = decompose_float_format(static_cast<Float>(min), min_exp);
242 // Intentionally reduce precision here. If we can past the float tests with our hands cuffed,
243 // then we can be pretty confident when working with doubles too. We should not need the double
244 // representation for this to be correct. Also "something something optimisation".
245
246 using sig_t = decltype(val_sig_raw);
247 using unsigned_sig_t = typename std::make_unsigned<sig_t>::type;
248 constexpr auto num_wider_bits = 2*sizeof(sig_t)*8;
249 using wider_t = std::bitset<num_wider_bits>;
250 const auto val_sign = std::signbit(val_sig_raw);
251 const auto res_sign = std::signbit(res_sig_raw);
252 const auto min_sign = std::signbit(min_sig_raw);
253
254 // Reexpress the values in a bitset of positive values
255 assert(!res_sign);
256 auto val_pos_sig = wider_t{static_cast<unsigned_sig_t>(std::abs(val_sig_raw))};
257 auto res_pos_sig = wider_t{static_cast<unsigned_sig_t>(res_sig_raw)};
258 auto min_pos_sig = wider_t{static_cast<unsigned_sig_t>(std::abs(min_sig_raw))};
259
260 // reexpress value, minimum, and resolution significands with minimum common exponent
261 auto common_exp = std::min({val_exp, min_exp, res_exp});
262
263 auto val_diff = val_exp - common_exp;
264 val_pos_sig <<= val_diff;
265 val_exp -= val_diff;
266
267 auto min_diff = min_exp - common_exp;
268 min_pos_sig <<= min_diff;
269 min_exp -= min_diff;
270
271 auto res_diff = res_exp - common_exp;
272 res_pos_sig <<= res_diff;
273 res_exp -= res_diff;
274
275 // Do the division
276 auto quant_val_sig = unsigned_quotient(val_pos_sig, res_pos_sig);
277 auto quant_val_exp = val_exp - res_exp;
278 auto quant_min_sig = unsigned_quotient(min_pos_sig, res_pos_sig);
279 auto quant_min_exp = min_exp - res_exp;
280
281 // Now we get them to exponent zero, rounding if necessary
282 if (quant_val_exp < 0) {
283 rounding_shift_right(quant_val_sig, static_cast<uint16_t>(-quant_val_exp));
284 } else {
285 quant_val_sig <<= quant_val_exp;
286 }
287 quant_val_exp = 0;
288 if (quant_min_exp < 0) {
289 rounding_shift_right(quant_min_sig, static_cast<uint16_t>(-quant_min_exp));
290 } else {
291 quant_min_sig <<= quant_min_exp;
292 }
293 quant_min_exp = 0;
294
295 // Apply signs
296 if (val_sign) {
297 negate(quant_val_sig);
298 }
299 if (min_sign) {
300 negate(quant_min_sig);
301 }
302
303 const auto value_enc_bits = difference(quant_val_sig, quant_min_sig);
304 // Encoding expected to be positive
305 assert(!is_negative(value_enc_bits));
306
307 const auto value_enc = fill_unsigned<unsigned_sig_t>(value_enc_bits);
308
309 return static_cast<uint64>(value_enc);
310}
311
312template <typename Integral, std::enable_if_t<std::is_integral<Integral>::value, bool> = true>
313Integral decode(uint64 value_enc, double min, double res) {
314 auto wire_value = static_cast<Integral>(value_enc);
315 if (res >= 1)
316 wire_value *= res;
317 else
318 wire_value /= (1.0 / res);
319
320 // round values again to properly handle cases where double precision
321 // leads to slightly off values (e.g. 2.099999999 instead of 2.1)
322 wire_value =
323 quantize(wire_value + quantize(static_cast<Integral>(min), res), res);
324 return wire_value;
325}
326
327template <typename Float, std::enable_if_t<std::is_floating_point<Float>::value, bool> = true>
328Float decode(uint64 value_enc, double min, double res) {
329 int16_t res_exp, min_exp;
330 auto res_sig_raw = decompose_float_format(static_cast<Float>(res), res_exp);
331 auto min_sig_raw = decompose_float_format(static_cast<Float>(min), min_exp);
332
333 using sig_t = decltype(res_sig_raw);
334 using unsigned_sig_t = typename std::make_unsigned<sig_t>::type;
335 constexpr auto num_narrow_bits = sizeof(sig_t)*8;
336 constexpr auto num_wider_bits = 2*num_narrow_bits;
337 using wider_t = std::bitset<num_wider_bits>;
338
339 const auto res_sign = std::signbit(res_sig_raw);
340 const auto min_sign = std::signbit(min_sig_raw);
341
342 // Reexpress the values in a bitset to express negative numbers
343 assert(!res_sign);
344 auto res_pos_sig = wider_t{static_cast<unsigned_sig_t>(res_sig_raw)};
345 auto min_pos_sig = wider_t{static_cast<unsigned_sig_t>(std::abs(min_sig_raw))};
346 const auto val_enc_sig = wider_t{value_enc};
347 const auto val_enc_exp = 0;
348
349 // Reexpress min and res to the lowest common exponent
350 const auto exp_diff = min_exp - res_exp;
351 if (exp_diff >= 0) {
352 min_pos_sig <<= exp_diff;
353 min_exp -= exp_diff;
354 } else {
355 res_pos_sig <<= -exp_diff;
356 res_exp -= -exp_diff;
357 }
358
359 auto quant_min_sig = unsigned_quotient(min_pos_sig, res_pos_sig);
360 auto quant_min_exp = min_exp - res_exp;
361
362 if (quant_min_exp < 0) {
363 rounding_shift_right(quant_min_sig, static_cast<uint16_t>(-quant_min_exp));
364 } else {
365 quant_min_sig <<= quant_min_exp;
366 }
367 quant_min_exp = 0;
368
369 // Apply signs
370 if (min_sign) {
371 negate(quant_min_sig);
372 }
373
374 auto sum_pos_bits = sum(val_enc_sig, quant_min_sig);
375 const auto sum_sign = is_negative(sum_pos_bits);
376 if (sum_sign) {
377 negate(sum_pos_bits);
378 }
379 const auto sum_exp = 0;
380
381 const auto sum_pos_raw_unsigned = fill_unsigned<unsigned_sig_t>(sum_pos_bits);
382
383 auto value = sum_pos_raw_unsigned * res;
384 if (sum_sign) {
385 value = -value;
386 }
387
388 // round values again to properly handle cases where double precision
389 // leads to slightly off values (e.g. 2.099999999 instead of 2.1)
390 value = quantize(value , res);
391
392 return static_cast<Float>(value);
393}
394
395} // namespace dccl
396#endif
Dynamic Compact Control Language namespace.
Definition any.h:28
google::protobuf::uint64 uint64
an unsigned 64 bit integer
Definition common.h:60
int32_t decompose_float_format(float val, int16_t &exponent)
Definition numeric.cpp:43
std::enable_if< std::is_floating_point< Float >::value, Float >::type quantize(Float value, double interval)
Definition common.h:90
float compose_float_format(int32_t significand, int16_t exponent)
Definition numeric.cpp:158