ChimeraTK-DeviceAccess  03.18.00
FixedPointConverter.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2 // SPDX-License-Identifier: LGPL-3.0-or-later
3 #pragma once
4 
5 #include "Exception.h"
6 #include "SupportedUserTypes.h"
7 #include <type_traits>
8 
9 #include <boost/fusion/algorithm.hpp>
10 #include <boost/fusion/container.hpp>
11 #include <boost/fusion/sequence.hpp>
12 #include <boost/numeric/conversion/cast.hpp>
13 
14 #include <cassert>
15 #include <cmath>
16 #include <cstdint>
17 #include <iostream>
18 #include <limits>
19 #include <sstream>
20 #include <stdexcept>
21 #include <string>
22 
23 namespace ChimeraTK {
24 
29  public:
43  explicit FixedPointConverter(
44  std::string variableName, unsigned int nBits = 32, int fractionalBits = 0, bool isSignedFlag = true);
45 
53  template<typename UserType>
54  uint32_t toRaw(UserType cookedValue) const;
55 
60  template<typename UserType, typename RAW_ITERATOR, typename COOKED_ITERATOR>
62  const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end, const COOKED_ITERATOR& cooked_begin) const {
63  static_assert(std::is_same<typename std::iterator_traits<RAW_ITERATOR>::iterator_category,
64  std::random_access_iterator_tag>::value,
65  "RAW_ITERATOR template argument must be a random access iterator.");
66  static_assert(std::is_same<typename std::iterator_traits<COOKED_ITERATOR>::iterator_category,
67  std::random_access_iterator_tag>::value,
68  "COOKED_ITERATOR template argument must be a random access iterator.");
69  static_assert(std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int8_t>::value ||
70  std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int16_t>::value ||
71  std::is_same<typename std::iterator_traits<RAW_ITERATOR>::value_type, int32_t>::value,
72  "RAW_ITERATOR template argument must be an iterator with value type equal to int8_t, int16_t or int32_t");
73  static_assert(std::is_same<typename std::iterator_traits<COOKED_ITERATOR>::value_type, UserType>::value,
74  "COOKED_ITERATOR template argument must be an iterator with value type equal to the UserType template "
75  "argument.");
76  vectorToCooked_impl<UserType, RAW_ITERATOR, COOKED_ITERATOR>::impl(*this, raw_begin, raw_end, cooked_begin);
77  }
78  template<typename UserType, typename RAW_ITERATOR, typename COOKED_ITERATOR>
80  static void impl(const FixedPointConverter& fpc, const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end,
81  COOKED_ITERATOR cooked_begin);
82  };
83 
85  template<typename UserType>
86  UserType scalarToCooked(int32_t const& raw) const {
87  UserType cooked;
88  vectorToCooked<UserType>(&raw, (&raw) + 1, &cooked);
89  return cooked;
90  }
91 
93  [[nodiscard]] unsigned int getNBits() const { return _nBits; }
94 
96  [[nodiscard]] int getFractionalBits() const { return _fractionalBits; }
97 
99  [[nodiscard]] bool isSigned() const { return _isSigned; }
100 
102  void reconfigure(unsigned int nBits = 32, int fractionalBits = 0, bool isSignedFlag = true);
103 
106  bool operator==(const FixedPointConverter& other) const {
107  return _nBits == other._nBits && _fractionalBits == other._fractionalBits && _isSigned == other._isSigned;
108  }
109  bool operator!=(const FixedPointConverter& other) const { return !operator==(other); }
110 
111  private:
112  std::string _variableName;
113  unsigned int _nBits;
114  int _fractionalBits;
115  bool _isSigned;
116 
119  double _fractionalBitsCoefficient;
120 
124  double _inverseFractionalBitsCoefficient;
125 
126  int32_t _signBitMask{};
127  int32_t _usedBitsMask{};
128  int32_t _unusedBitsMask{};
129  int32_t _bitShiftMask{};
130  int32_t _bitShiftMaskSigned{};
132 
134  int32_t _maxRawValue{};
135  int32_t _minRawValue{};
136 
138  userTypeMap _maxCookedValues;
139 
141  userTypeMap _minCookedValues;
142 
145  FixedUserTypeMap<int> conversionBranch_toCooked;
146 
149  const static int zero;
150 
152  class initCoefficients {
153  public:
154  explicit initCoefficients(FixedPointConverter* fpc) : _fpc(fpc) {}
155 
156  template<typename Pair>
157  void operator()(Pair) const {
158  // obtain UserType from given fusion::pair type
159  typedef typename Pair::first_type UserType;
160 
161  // compute conversion branches. Needs to be done before the subsequent calls to toCooked()!
162  if(_fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && !_fpc->_isSigned) {
163  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 9;
164  }
165  else if(_fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && _fpc->_isSigned) {
166  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 10;
167  }
168  else if(std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && !_fpc->_isSigned) {
169  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 1;
170  }
171  else if(std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && _fpc->_isSigned) {
172  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 2;
173  }
174  else if(_fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && !_fpc->_isSigned) {
175  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 7;
176  }
177  else if(_fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && _fpc->_isSigned) {
178  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 8;
179  }
180  else if(_fpc->_nBits == 16 && !_fpc->_isSigned) {
181  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 5;
182  }
183  else if(_fpc->_nBits == 16 && _fpc->_isSigned) {
184  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 6;
185  }
186  else if(!_fpc->_isSigned) {
187  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 3;
188  }
189  else if(_fpc->_isSigned) {
190  boost::fusion::at_key<UserType>(_fpc->conversionBranch_toCooked.table) = 4;
191  }
192 
193  // compute minimum and maximum values in cooked representation
194  try {
195  boost::fusion::at_key<UserType>(_fpc->_minCookedValues) = _fpc->scalarToCooked<UserType>(_fpc->_minRawValue);
196  }
197  catch(boost::numeric::negative_overflow& e) {
198  boost::fusion::at_key<UserType>(_fpc->_minCookedValues) = std::numeric_limits<UserType>::min();
199  }
200  try {
201  boost::fusion::at_key<UserType>(_fpc->_maxCookedValues) = _fpc->scalarToCooked<UserType>(_fpc->_maxRawValue);
202  }
203  catch(boost::numeric::positive_overflow& e) {
204  boost::fusion::at_key<UserType>(_fpc->_maxCookedValues) = std::numeric_limits<UserType>::max();
205  }
206  }
207 
208  private:
209  FixedPointConverter* _fpc;
210  };
211 
213  template<class S>
214  struct Round {
215  static S nearbyint(S s) { return round(s); }
216 
217  using round_style = boost::mpl::integral_c<std::float_round_style, std::round_to_nearest>;
218  };
219 
222  template<typename UserType, typename std::enable_if<std::is_signed<UserType>{}, int>::type = 0>
223  bool isNegativeUserType(UserType value) const;
224  template<typename UserType, typename std::enable_if<!std::is_signed<UserType>{}, int>::type = 0>
225  bool isNegativeUserType(UserType value) const;
226 
227  // helper function: force unused leading bits to 0 for positive or 1 for negative numbers
228  // NOLINTBEGIN(hicpp-signed-bitwise)
229  // NOLINTBEGIN(bugprone-narrowing-conversions)
230  // Turn off the linter warning. Yes, we are fiddling with the bit interpretation here, that's the whole point.
231  void padUnusedBits(int32_t& rawValue) const {
232  if(!(rawValue & _signBitMask)) {
233  rawValue &= _usedBitsMask;
234  }
235  else {
236  rawValue |= _unusedBitsMask;
237  }
238  }
239  // NOLINTEND(bugprone-narrowing-conversions)
240  // NOLINTEND(hicpp-signed-bitwise)
241  };
242 
243  /********************************************************************************************************************/
244 
245  // FIXME: replace reinterpret_cast with bit_cast once C++20 is available for us.
246  // Until then, turn off the linter warning about reinterpret_cast
247  // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
248  template<typename UserType, typename RAW_ITERATOR, typename COOKED_ITERATOR>
250  const FixedPointConverter& fpc, const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end,
251  COOKED_ITERATOR cooked_begin) {
252  // Handle integer and floating-point types differently.
253  switch(boost::fusion::at_key<UserType>(fpc.conversionBranch_toCooked.table)) {
254  case 1: { // std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && !_fpc->_isSigned
255  std::transform(raw_begin, raw_end, cooked_begin, [&fpc](int32_t rawValue) {
256  fpc.padUnusedBits(rawValue);
257  return numericToUserType<UserType>(*(reinterpret_cast<uint32_t*>(&rawValue)));
258  });
259  break;
260  }
261  case 2: { // std::numeric_limits<UserType>::is_integer && _fpc->_fractionalBits == 0 && _fpc->_isSigned
262  std::transform(raw_begin, raw_end, cooked_begin, [&fpc](int32_t rawValue) {
263  fpc.padUnusedBits(rawValue);
264  return numericToUserType<UserType>(rawValue);
265  });
266  break;
267  }
268  case 9: { // _fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && !_fpc->_isSigned
269  std::transform(raw_begin, raw_end, cooked_begin, [](const auto& rawValue) {
270  return numericToUserType<UserType>(*(reinterpret_cast<const uint16_t*>(&rawValue)));
271  });
272  break;
273  }
274  case 10: { // _fpc->_nBits == 16 && _fpc->_fractionalBits == 0 && _fpc->_isSigned
275  std::transform(raw_begin, raw_end, cooked_begin, [](const auto& rawValue) {
276  return numericToUserType<UserType>(*(reinterpret_cast<const int16_t*>(&rawValue)));
277  });
278  break;
279  }
280  case 7: { // _fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && !_fpc->_isSigned
281  const auto f = static_cast<uint32_t>(fpc._fractionalBitsCoefficient);
282  std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
283  return numericToUserType<UserType>(f * *(reinterpret_cast<const uint16_t*>(&rawValue)));
284  });
285  break;
286  }
287  case 8: { // _fpc->_nBits == 16 && _fpc->_fractionalBits < 0 && _fpc->_fractionalBits > -16 && _fpc->_isSigned
288  const auto f = static_cast<int32_t>(fpc._fractionalBitsCoefficient);
289  std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
290  return numericToUserType<UserType>(f * *(reinterpret_cast<const int16_t*>(&rawValue)));
291  });
292  break;
293  }
294  case 5: { // _fpc->_nBits == 16 && !_fpc->_isSigned
295  const auto f = fpc._fractionalBitsCoefficient;
296  std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
297  return numericToUserType<UserType>(f * *(reinterpret_cast<const uint16_t*>(&rawValue)));
298  });
299  break;
300  }
301  case 6: { // _fpc->_nBits == 16 && _fpc->_isSigned
302  const auto f = fpc._fractionalBitsCoefficient;
303  std::transform(raw_begin, raw_end, cooked_begin, [f](const auto& rawValue) {
304  return numericToUserType<UserType>(f * *(reinterpret_cast<const int16_t*>(&rawValue)));
305  });
306  break;
307  }
308  case 3: { // !_fpc->_isSigned
309  const auto f = fpc._fractionalBitsCoefficient;
310  std::transform(raw_begin, raw_end, cooked_begin, [&fpc, f](int32_t rawValue) {
311  fpc.padUnusedBits(rawValue);
312  return numericToUserType<UserType>(f * *(reinterpret_cast<uint32_t*>(&rawValue)));
313  });
314  break;
315  }
316  case 4: { // _fpc->_isSigned
317  const auto f = fpc._fractionalBitsCoefficient;
318  std::transform(raw_begin, raw_end, cooked_begin, [&fpc, f](int32_t rawValue) {
319  fpc.padUnusedBits(rawValue);
320  auto ttt = numericToUserType<UserType>(f * rawValue);
321  return ttt;
322  });
323  break;
324  }
325  default: {
326  std::cerr << "Fixed point converter configuration is corrupt." << std::endl;
327  std::terminate();
328  }
329  }
330  }
331  // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
332 
333  /********************************************************************************************************************/
334 
335  template<typename UserType>
336  uint32_t FixedPointConverter::toRaw(UserType cookedValue) const {
337  // Do a range check first. The later overflow check in the conversion is not
338  // sufficient, since we can have non-standard word sizes like 12 bits.
339  if(cookedValue < boost::fusion::at_key<UserType>(_minCookedValues)) {
340  return _minRawValue;
341  }
342  if(cookedValue > boost::fusion::at_key<UserType>(_maxCookedValues)) {
343  return _maxRawValue;
344  }
345 
346  // handle integer and floating-point types differently
347  if(std::numeric_limits<UserType>::is_integer && _fractionalBits == 0) {
348  // extract the sign and leave the positive number
349  bool isNegative = isNegativeUserType(cookedValue);
350  if(isNegative && !_isSigned) return _minRawValue;
351  if(isNegative) {
352  cookedValue = -(cookedValue + 1); // bit inversion, ~ operator cannot be used as UserType might be double
353  }
354  // cast into raw type
355  auto rawValue = static_cast<uint32_t>(cookedValue);
356 
357  // handle sign
358  if(_isSigned && isNegative) {
359  rawValue = ~rawValue;
360  }
361 
362  // return with bit mask applied
363  return rawValue & static_cast<uint32_t>(_usedBitsMask);
364  }
365  // convert into double and scale by fractional bit coefficient
366  double d_cooked = _inverseFractionalBitsCoefficient * static_cast<double>(cookedValue);
367 
368  // Convert into either signed or unsigned int32_t, depending on _isSigned,
369  // so the conversion handles the sign correctly. Store always in a uint32_t,
370  // since this is our raw type. The conversion will properly round, when
371  // needed. Negative overflow exceptions need to be caught for some corner
372  // cases (e.g. number of fractional bits >= number of bits in total).
373  // Positive overflow cannot happen due to the range check above (the negative
374  // branch has one more possible value).
375  int32_t raw;
376  try {
377  if(_isSigned) {
378  typedef boost::numeric::converter<int32_t, double, boost::numeric::conversion_traits<int32_t, double>,
379  boost::numeric::def_overflow_handler, Round<double>>
380  converter_signed;
381  raw = converter_signed::convert(d_cooked);
382  }
383  else {
384  typedef boost::numeric::converter<uint32_t, double, boost::numeric::conversion_traits<uint32_t, double>,
385  boost::numeric::def_overflow_handler, Round<double>>
386  converter_unsigned;
387  raw = static_cast<int32_t>(converter_unsigned::convert(d_cooked));
388  }
389  }
390  catch(boost::numeric::negative_overflow& e) {
391  raw = _minRawValue;
392  }
393 
394  // apply bit mask
395  // NOLINTNEXTLINE(hicpp-signed-bitwise)
396  return raw & _usedBitsMask;
397  }
398 
399  /********************************************************************************************************************/
400 
401  template<typename UserType, typename std::enable_if<std::is_signed<UserType>{}, int>::type>
402  bool FixedPointConverter::isNegativeUserType(UserType value) const {
403  return static_cast<bool>(value < 0);
404  }
405 
406  template<typename UserType, typename std::enable_if<!std::is_signed<UserType>{}, int>::type>
407  bool FixedPointConverter::isNegativeUserType(UserType /*value*/) const {
408  return false;
409  }
410 
411  /********************************************************************************************************************/
412 
413  template<typename RAW_ITERATOR, typename COOKED_ITERATOR>
414  struct FixedPointConverter::vectorToCooked_impl<std::string, RAW_ITERATOR, COOKED_ITERATOR> {
415  static void impl(const FixedPointConverter& fpc, const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end,
416  COOKED_ITERATOR cooked_begin) {
417  if(fpc._fractionalBits == 0) { // use integer conversion
418  if(fpc._isSigned) {
419  std::vector<int32_t> intValues(raw_end - raw_begin);
420  fpc.vectorToCooked<int32_t>(raw_begin, raw_end, intValues.begin());
421  for(auto it : intValues) {
422  *cooked_begin = std::to_string(it);
423  ++cooked_begin;
424  }
425  }
426  else {
427  std::vector<uint32_t> uintValues(raw_end - raw_begin);
428  fpc.vectorToCooked<uint32_t>(raw_begin, raw_end, uintValues.begin());
429  for(auto it : uintValues) {
430  *cooked_begin = std::to_string(it);
431  ++cooked_begin;
432  }
433  }
434  }
435  else {
436  std::vector<double> doubleValues(raw_end - raw_begin);
437  fpc.vectorToCooked<double>(raw_begin, raw_end, doubleValues.begin());
438  for(auto it : doubleValues) {
439  *cooked_begin = std::to_string(it);
440  ++cooked_begin;
441  }
442  }
443  }
444  };
445 
446  /********************************************************************************************************************/
447  template<>
448  [[nodiscard]] uint32_t FixedPointConverter::toRaw<std::string>(std::string cookedValue) const;
449 
450  template<>
451  [[nodiscard]] uint32_t FixedPointConverter::toRaw<Boolean>(Boolean cookedValue) const;
452 
453  template<>
454  [[nodiscard]] uint32_t FixedPointConverter::toRaw<Void>(__attribute__((unused)) Void cookedValue) const;
455 
456 } // namespace ChimeraTK
ChimeraTK::Void
Wrapper Class for void.
Definition: SupportedUserTypes.h:71
ChimeraTK::FixedPointConverter::vectorToCooked
void vectorToCooked(const RAW_ITERATOR &raw_begin, const RAW_ITERATOR &raw_end, const COOKED_ITERATOR &cooked_begin) const
Conversion function from fixed-point values to type T.
Definition: FixedPointConverter.h:61
ChimeraTK::FixedPointConverter::getFractionalBits
int getFractionalBits() const
Read back the fractional bits the converter is using.
Definition: FixedPointConverter.h:96
ChimeraTK::AccessMode::raw
@ raw
Raw access: disable any possible conversion from the original hardware data type into the given UserT...
ChimeraTK::FixedPointConverter::scalarToCooked
UserType scalarToCooked(int32_t const &raw) const
Inefficient convenience function for converting a single value to cooked.
Definition: FixedPointConverter.h:86
ChimeraTK::FixedPointConverter::vectorToCooked_impl
Definition: FixedPointConverter.h:79
SupportedUserTypes.h
ChimeraTK::userTypeMap
boost::fusion::map< boost::fusion::pair< int8_t, int8_t >, boost::fusion::pair< uint8_t, uint8_t >, boost::fusion::pair< int16_t, int16_t >, boost::fusion::pair< uint16_t, uint16_t >, boost::fusion::pair< int32_t, int32_t >, boost::fusion::pair< uint32_t, uint32_t >, boost::fusion::pair< int64_t, int64_t >, boost::fusion::pair< uint64_t, uint64_t >, boost::fusion::pair< float, float >, boost::fusion::pair< double, double >, boost::fusion::pair< std::string, std::string >, boost::fusion::pair< Boolean, Boolean >, boost::fusion::pair< Void, Void > > userTypeMap
Map of UserType to value of the UserType.
Definition: SupportedUserTypes.h:381
ChimeraTK::FixedPointConverter::operator!=
bool operator!=(const FixedPointConverter &other) const
Definition: FixedPointConverter.h:109
ChimeraTK::FixedPointConverter::vectorToCooked_impl::impl
static void impl(const FixedPointConverter &fpc, const RAW_ITERATOR &raw_begin, const RAW_ITERATOR &raw_end, COOKED_ITERATOR cooked_begin)
Definition: FixedPointConverter.h:249
ChimeraTK::FixedPointConverter::reconfigure
void reconfigure(unsigned int nBits=32, int fractionalBits=0, bool isSignedFlag=true)
Reconfigure the fixed point converter with new type information.
Definition: FixedPointConverter.cc:23
ChimeraTK::FixedPointConverter::isSigned
bool isSigned() const
Read back wether the conversion is using signed values.
Definition: FixedPointConverter.h:99
ChimeraTK::FixedPointConverter::FixedPointConverter
FixedPointConverter(std::string variableName, unsigned int nBits=32, int fractionalBits=0, bool isSignedFlag=true)
The constructor defines the conversion factor.
Definition: FixedPointConverter.cc:14
ChimeraTK::FixedUserTypeMap::table
boost::fusion::map< boost::fusion::pair< int8_t, TargetType >, boost::fusion::pair< uint8_t, TargetType >, boost::fusion::pair< int16_t, TargetType >, boost::fusion::pair< uint16_t, TargetType >, boost::fusion::pair< int32_t, TargetType >, boost::fusion::pair< uint32_t, TargetType >, boost::fusion::pair< int64_t, TargetType >, boost::fusion::pair< uint64_t, TargetType >, boost::fusion::pair< float, TargetType >, boost::fusion::pair< double, TargetType >, boost::fusion::pair< std::string, TargetType >, boost::fusion::pair< Boolean, TargetType >, boost::fusion::pair< Void, TargetType > > table
Definition: SupportedUserTypes.h:402
ChimeraTK::FixedPointConverter::operator==
bool operator==(const FixedPointConverter &other) const
Compare two fixed point converters.
Definition: FixedPointConverter.h:106
ChimeraTK::FixedPointConverter
The fixed point converter provides conversion functions between a user type and up to 32 bit fixed po...
Definition: FixedPointConverter.h:28
ChimeraTK::FixedPointConverter::getNBits
unsigned int getNBits() const
Read back the number of bits the converter is using.
Definition: FixedPointConverter.h:93
ChimeraTK::FixedPointConverter::vectorToCooked_impl< std::string, RAW_ITERATOR, COOKED_ITERATOR >::impl
static void impl(const FixedPointConverter &fpc, const RAW_ITERATOR &raw_begin, const RAW_ITERATOR &raw_end, COOKED_ITERATOR cooked_begin)
Definition: FixedPointConverter.h:415
Exception.h
ChimeraTK::FixedPointConverter::toRaw
uint32_t toRaw(UserType cookedValue) const
Conversion function from type T to fixed point.
Definition: FixedPointConverter.h:336
ChimeraTK
Definition: DummyBackend.h:16
ChimeraTK::to_string
std::string to_string(Boolean &value)
Definition: SupportedUserTypes.h:59
ChimeraTK::Boolean
Wrapper Class to avoid vector<bool> problems.
Definition: SupportedUserTypes.h:21