0
void Manager::byteArrayToDoubleArray(byte ch[]) {
  int counter = 0;
  // temp array to break the byte array into size of 8 and read it
  byte temp[64];
  // double result values
  double res[8];
  int index = 0;
  int size = (sizeof(ch) / sizeof(*ch));
  for (int i = 0; i < size; i++) {
    counter++;
    temp[i] = ch[i];
    if (counter % 8 == 0) {
      res[index] = *reinterpret_cast<double * const>(temp);
      index++;
      counter = 0;
    }
  }
}

Here result would be a list of double values with count = 8.

dda
  • 6,030
  • 2
  • 25
  • 34
deepu
  • 73
  • 1
  • 4
  • 7
    `int size = (sizeof(ch)/sizeof(*ch));` doesn't do what you think it does. Have a look here: [What is array decaying?](https://stackoverflow.com/questions/1461432/what-is-array-decaying); –  Feb 02 '18 at 21:04
  • You didn't say which Arduino you have, but [`double`](https://www.arduino.cc/reference/en/language/variables/data-types/double/) might be only 4 bytes, the same as `float`. – gre_gor Feb 03 '18 at 00:40
  • You cannot directly `reinterpret_cast` to `double*` while there is no actual `double` object. It results in undefined behavior. – xskxzr Feb 03 '18 at 08:45
  • @xskxzr You are right that it is UB (the right thing to do is memcpy into a double), but I would be surprised if it didn't work. – Martin Bonner supports Monica Feb 03 '18 at 10:15
  • @MartinBonner Some optimization techniques are based on this UB. [This answer](https://stackoverflow.com/a/99010/5376789) explains well how this UB benefits an optimizer. – xskxzr Feb 03 '18 at 10:26

3 Answers3

1

Your problem is two things. You have some typos and misunderstanding. And the C++ standard is somewhat broken in this area.

I'll try to fix both.

First, a helper function called laundry_pods. It takes raw memory and "launders" it into an array of a type of your choice, so long as you pick a pod type:

template<class T, std::size_t N>
T* laundry_pods( void* ptr ) {
  static_assert( std::is_pod<std::remove_cv_t<T>>{} );
  char optimized_away[sizeof(T)*N];
  std::memcpy( optimized_away, ptr , sizeof(T)*N );
  T* r = ::new( ptr ) T[N];
  assert( r == ptr );
  std::memcpy( r, optimized_away, sizeof(T)*N );
  return r;
}

now simply do

void Manager::byteArrayToDoubleArray(byte ch[]) {
  double* pdouble = laundry_pods<double, 8>(ch);
}

and pdouble is a pointer to memory of ch interpreted as an array of 8 doubles. (It is not a copy of it, it interprets those bytes in-place).

While laundry_pods appears to copy the bytes around, both g++ and clang optimize it down into a binary noop. The seeming copying of bytes around is a way to get around aliasing restrictions and object lifetime rules in the C++ standard.

It relies on arrays of pod not having extra bookkeeping overhead (which C++ implementations are free to do; none do that I know of. That is what the non-static assert double-checks), but it returns a pointer to a real honest to goodness array of double. If you want to avoid that assumption, you could instead create each doulbe as a separate object. However, then they aren't an array, and pointer arithmetic over non-arrays is fraught as far as the standard is concerned.

The use of the term "launder" has to do with getting around aliasing and object lifetime requirements. The function does nothing at runtime, but in the C++ abstract machine it takes the memory and converts it into binary identical memory that is now a bunch of doubles.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

The trick of doing this kind of "conversion" is to always cast the double* to a char* (or unsigned char or std::byte). Never the other way round.

You should be able to do something like this:

void byteArrayToDoubleArray(byte* in, std::size_t n, double* out)
{
    for(auto out_bytes = (byte*) out; n--;)
        *out_bytes++ = *in++;
}

// ...

byte ch[64];

// .. fill ch with double data somehow

double res[8];

byteArrayToDoubleArray(ch, 64, res);

Assuming that type byte is an alias of char or unsigned char or std::byte.

Galik
  • 47,303
  • 4
  • 80
  • 117
  • `char`'s signness might very well be unsigned in some implementation. – Mário Feroldi Feb 03 '18 at 05:06
  • 1
    @MárioFeroldi As long as you don't do any math on it or try to convert it the sign doesn't matter. All this solution does it make copies in such a way as to avoid *strict aliasing* problems. – Galik Feb 03 '18 at 10:09
  • Thanks so much for the update! I tried the solution but the res here is not initialized float res[8]; byteArrayToDoubleArray(buffer, 32, res); for (int j = 0; j < 8; j++) { print res[j]; } void byteArrayToDoubleArray(byte* in, std::size_t n, float* out) { for(auto out_bytes = (byte*) out; n--;) *out_bytes++ = *in++; } – deepu Feb 13 '18 at 21:36
  • Am I missing out something? I'm new to C++ so I'm really not sure how the 'out' is getting initialized in the above function. I will do my homework and update you! Thanks again – deepu Feb 13 '18 at 21:41
  • @deepu `out` receives the buffer defined `double res[8];` in the call at the bottom. – Galik Feb 13 '18 at 23:08
0

I am not completly sure what you are trying to achieve here because of the code (sizeof(ch) / sizeof(*ch)) which does not make sense for an array of undefined size.

If you have a byte-Array (POD data type; something like a typedef char byte;) then this most simple solution would be a reinterpret_cast:

double *result = reinterpret_cast<double*>(ch);

This allows you to use result[0]..result[7] as long as ch[] is valid and contains at least 64 bytes. Be aware that this construct does not generate code. It tells the compiler that result[0] corresponds to ch[0..7] and so on. An access to result[] will result in an access to ch[].

But you have to know the number of elements in ch[] to calculate the number of valid double elements in result.

If you need a copy (because - for example - the ch[] is a temporary array) you could use

std::vector<double> result(reinterpret_cast<double*>(ch), reinterpret_cast<double*>(ch) + itemsInCh * sizeof(*ch) / sizeof(double));

So if ch[] is an array with 64 items and a byte is really an 8-bit value, then

std::vector<double> result(reinterpret_cast<double*>(ch), reinterpet_cast<double*>(ch) + 8);

will provide a std::vector containing 8 double values.

There is another possible method using a union:

union ByteToDouble
{
  byte b[64];
  double d[8];
} byteToDouble;

the 8 double values will occupie the same memory as the 64 byte values. So you can write the byte values to byteToDouble.b[] and read the resultingdouble values from byteToDouble.d[].

Andreas H.
  • 1,757
  • 12
  • 24
  • Thank you so much for the response and my apologies for replying this late. I'm new to C++, I tried using the union ByteToDouble { byte b[64]; double d[8]; } byteToDouble; but no luck. All I'm doing is initializing the byte array and then and tryin to read the d[i] but its zero for all the items in the double array. – deepu Feb 13 '18 at 21:24
  • I tried it again and it works for me. Did you use the same byteToDouble Instance for the test? If you use your Debugger to look at byteToDouble.b and byteToDouble.d both array should start at the same memory address. – Andreas H. Feb 15 '18 at 14:09