Louis Cloete
Louis Cloete

Reputation: 451

Is there a well-defined and performant way to bitwise convert double to uint64_t and back

Previous similar questions which didn't answer my question:

bit cast uint64_t to double and back in a macro

Converting uint64_t to Double Value

I need to save a double to the flash memory on an STM32L476 microcontroller. The flash controller works in 64 bit chunks and the HAL APIs from ST take a uint64_t parameter for the data to write. To this end, I need to convert the bits from the double variable into one or more uint64_ts.

I tried the following, which is UB:

uint64_t flash_write_val = *(uint64_t*)&double_value;

However, I get a compiler warning about breaking strict aliasing rules. How would I do this without invoking UB? It doesn't need to be super portable. It can be valid only on a Cortex M4F core.

I'm thinking about this:

union uint64_double {
    uint64_t flash_friendly;
    double math_friendly;
};

Is this a good approach, or am I still shooting myself in the foot?

Upvotes: 1

Views: 573

Answers (3)

Flow
Flow

Reputation: 1

Here is the code I use :

template <typename DestT, typename SrcT>
inline constexpr DestT reinterpretType(SrcT src){
    DestT dest;
    memcpy(&dest, &src, sizeof(DestT));
    return dest;
    static_assert(sizeof(DestT) == sizeof(SrcT));
}

Don't forget to declare it as static if you use it in a class. And curiously, GCC does not complain about memcpy in a constexpr exrpession (probably the compiler optimize it). Note : it's not necessary to declare the second typename :

double d = 1.0d;
uint64_t asUint64 = reinterpretType<uint64_t>(d);
float f = 1.0f;
uint32_t asUint32 = reinterpretType<uint32_t>(f);

Upvotes: 0

0___________
0___________

Reputation: 67546

It is only Cortex-M3+ answer!!

  1. Pointer punning.
uint64_t flash_write_val = *(uint64_t*)&double_value;

As Cortex-M3 and newer support not-aligned access the above is 100% safe. I personally prefer memcpy way, but in FLASH write functions I usually use pointer punning.

  1. Union punning - safe and portable:
union uint64_double {
    uint64_t flash_friendly;
    double math_friendly;
};
  1. The memcpy function The most portable and and probably most efficient method as modern compilers know very well what memcpy does and in many cases inline it.
uint64_t bar(uint64_t *);

uint64_t foo(double double_val)
{
    uint64_t flash_write_val;
    memcpy(&flash_write_val, &double_val, sizeof(flash_write_val));
    return bar(&flash_write_val);
}
foo:
        push    {lr}
        sub     sp, sp, #12
        mov     r2, r0
        mov     r3, r1
        mov     r0, sp
        strd    r2, [sp]
        bl      bar
        add     sp, sp, #12
        ldr     pc, [sp], #4

https://godbolt.org/z/Ws6Yr15x8

Upvotes: -1

dbush
dbush

Reputation: 224082

Just use memcpy to copy the bytes where you want.

memcpy(&flash_write_val, &double_val, sizeof(double_val));

Upvotes: 9

Related Questions