Search code examples
cceedling

Convert Long To Double, Unexpected Results


I am using very basic code to convert a string into a long and into a double. The CAN library I am using requires a double as an input. I am attempting to send the device ID as a double to another device on the CAN network.

If I use an input string of that is 6 bytes long the long and double values are the same. If I add a 7th byte to the string the values are slightly different.

I do not think I am hitting a max value limit. This code is run with ceedling for an automated test. The same behaviour is seen when sending this data across my CAN communications. In main.c the issue is not observed.

The test is:

void test_can_hal_get_spn_id(void){
    struct dbc_id_info ret;
    memset(&ret, NULL_TERMINATOR, sizeof(struct dbc_id_info));

    char expected_str[8] = "smg123";
    char out_str[8];
    memset(&out_str, 0, 8);

    uint64_t long_val = 0;
    double phys = 0.0;
    memcpy(&long_val, expected_str, 8);
    phys = long_val;

    printf("long %ld \n", long_val);
    printf("phys %f \n", phys);

    uint64_t temp = (uint64_t)phys;
    memcpy(&out_str, &temp, 8);
    printf("%s\n", expected_str);
    printf("%s\n", out_str);
}

With the input = "smg123"

[test_can_hal.c]
  - "long 56290670243187 "
  - "phys 56290670243187.000000 "
  - "smg123"
  - "smg123"

With the input "smg1234"

[test_can_hal.c]
  - "long 14692989459197299 "
  - "phys 14692989459197300.000000 "
  - "smg1234"
  - "tmg1234"

Is this error just due to how floats are handled and rounded? Is there a way to test for that? Am I doing something fundamentally wrong?

Representing the char array as a double without the intermediate long solved the issue. For clarity I am using DBCPPP. I am using it in C. I should clarify my CAN library comes from NXP, DBCPPP allows my application to read a DBC file and apply the data scales and factors to my raw CAN data. DBCPPP accepts doubles for all data being encoded and returns doubles for all data being decoded.


Solution

  • The CAN library I am using requires a double as an input.

    That sounds surprising, but if so, then why are you involving a long as an intermediary between your string and double?

    If I use an input string of that is 6 bytes long the long and double values are the same. If I add a 7th byte to the string the values are slightly different.

    double is a floating point data type. To be able to represent values with a wide range of magnitudes, some of its bits are used to represent scale, and the rest to represent significant digits. A typical C implementation uses doubles with 53 bits of significand. It cannot exactly represent numbers with more than 53 significant binary digits. That's enough for 6 bytes, but not enough for 7.

    I do not think I am hitting a max value limit.

    Not a maximum value limit. A precision limit. A 64-bit long has smaller numeric range but more significant digits than an IEEE-754 double.

    So again, what role is the long supposed to be playing in your code? If the objective is to get eight bytes of arbitrary data into a double, then why not go directly there? Example:

        char expected_str[8] = "smg1234";
        char out_str[8] = {0};
    
        double phys = 0.0;
        memcpy(&phys, expected_str, 8);
    
        printf("phys %.14e\n", phys);
    
        memcpy(&out_str, &phys, 8);
        printf("%s\n", expected_str);
        printf("%s\n", out_str);
    

    Do note, however, that there is some risk when (mis)using a double this way. It is possible for the data you put in to constitute a trap representation (a signaling NaN might be such a representation, for example). Handling such a value might cause a trap, or cause the data to be corrupted, or possibly produce other misbehavior. It is also possible to run into numeric issues similar to the one in your original code.

    Possibly your library provides some relevant guarantees in that area. I would certainly hope so if doubles are really its sole data type for communication. Otherwise, you could consider using multiple doubles to covey data payloads larger than 53 bits, each of which you could consider loading via your original technique.