Search code examples
ccastingdoubleunsigned-long-long-int

C how to retrieve exact double value (precision) from a unsigned integer


I have a checksum which I am trying to reverse engineer, I already know how to keep the checksum generating after I know the original initial value that was used to create the checksum in the first place.

So far I know the checksum is generated using a double datatype mathematical equation.

The final value for the computed checksum is a unsigned integer, but before that it gets converted from double to unsigned long long aka (unsigned __int64).

What I'm trying to do is get a unsigned integer back into the same value in double datatype to get to the next step of undoing the checksum to retrieve the original initial value.

While the checksum is computing here is the value it generates in double datatype. 3083570000.3115764 which creates a checksum 0xB7CB8B50

I don't think this is a lossy conversation so nothing is really lost even though it converts from 8 bytes double back down 4 bytes integer checksum. Why? because the double value is always created by multiplying with 4294967295.0 which I believe is only to eliminate the trailing 4 bytes kinda like a shift.

So the double value i'm looking to retrieve will have to be divided by 4294967295.0 to exactly get back the original double value to the last digit.

Problem is I cannot divide it properly because it's not 100% accurate to the last decimal point.. I know with floating-point math it's not accurate 100% with the IEEE floating point crap, but I don't care about that I'm just trying to reverse this this the same way it was created in the first place.

Outputs Say the original checksum double was 0.71794958809146792
0.71794958809146792 * 4294967295.0 = 3083570000.3115761849416764
the answer that's sent in the packet is 0xb7cb8b50

if I cast the unsigned integer 0xb7cb8b50 to unsigned __int64 manually by hand it should look like this 0x00000000b7cb8b50

The original double how it was generated in code should look like this, I used the same key before it was appended to packet to recreate the same conditions to make the checksum in the first place and it should look like this
Real ANSWER = 3083570000.3115764

So
0x00000000b7cb8b50 should equals = 3083570000.3115764 double

My reverse code looks like this

unsigned int checksum = 0xb7cb8b50;
double test1 = (double)(unsigned __int64)checksum;
double test2 = double(checksum);
double test3 = static_cast<double>(checksum);
double test4 = *((double*)(void*)&checksum);

the code above is wrong by a few decimal places.

test1 returns = 3083570000.0000000
test2 returns = 3083570000.0000000
test3 returns = 3083570000.0000000
test4 returns = 1.523486003547e-314#DEN

How do I obtain the extra, .3115764 as well is my question.


Solution

  • The fractional part (after the decimal point) of a double number is lost when it is assigned to an integer.

    Doing the reverse operation (assigning the resulted int to a double), the fractional part will be 0. So it is not possible to recover back the original value of the double number.

    In the output of the following code it can be seen how the double value is stored in memory (the most significant 32 bits are not 0):

    double initial = 0.71794958809146792 * 4294967295.0;
    uint64_t rawValue = *(uint64_t*)&initial;
    uint32_t checksum = (uint32_t)initial;
    printf("initial: %f 0x%llx\n", initial, rawValue);
    printf("after:   %u 0x%x\n", checksum, checksum);
    // Prints
    // initial: 3083570000.311576 0x41e6f9716a09f86f
    // after:   3083570000 0xb7cb8b50
    

    Trying to cast the raw int value to double (as in *((double*)(void*)&checksum)) is an incorrect operation, and it may even access memory not belonging to checksum (if int size if 4 bytes).

    More information about double representation can be found at:

    https://en.wikipedia.org/wiki/Double-precision_floating-point_format