0

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.

Michael
  • 399
  • 2
  • 17
  • 10
    What kind of *bizarro* library requires a double to represent a device ID??? – paxdiablo Oct 24 '22 at 22:54
  • 2
    An IEEE-754 `double` has between 14 and 15 digits of accuracy. `14692989459197299` is _not_ guaranteed to be representable by a `double`. – Drew Dormann Oct 24 '22 at 22:57
  • "I do not think I am hitting a max value limit." Why? Did you try to check the relevant standard? What is your understanding of how the `double` type works? – Karl Knechtel Oct 24 '22 at 23:16
  • Does this answer your question? [Find max integer size that a floating point type can handle without loss of precision](https://stackoverflow.com/questions/2582032/find-max-integer-size-that-a-floating-point-type-can-handle-without-loss-of-prec) – Karl Knechtel Oct 24 '22 at 23:16
  • What is CAN, and how is it relevant to the question? – Karl Knechtel Oct 24 '22 at 23:19
  • @KarlKnechtel [CAN is a communications protocol and physical interface commonly used in automobiles](https://en.wikipedia.org/wiki/CAN_bus). It's relevant because OP is apparently using a poorly designed CAN API to control the CAN bus. – user3386109 Oct 24 '22 at 23:40
  • 1
    CAN bus uses 29-bit device identifiers, which should fit quite comfortably into a normal 32-bit int. I think you misunderstand your library. – Lee Daniel Crocker Oct 24 '22 at 23:46

2 Answers2

1

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    Nitpick with the final paragraph: CAN bus messages have a maximum payload size of 8 bytes. So one `double` fills the entire payload. Using multiple `double`s means sending multiple packets. I personally think that OP has completely misunderstood how the API works, and is trying to use a hammer to fix a leaky faucet. In other words, I have no idea what the OP is actually trying to accomplish, but there's seem to be an [XY-Problem](https://en.wikipedia.org/wiki/XY_problem) involved. – user3386109 Oct 25 '22 at 00:58
0

If you have a look at the IEEE-754 Wikipedia page, you'll see that the double precision values have a precision of "[a]pproximately 16 decimal digits". And that's roughly where your problem seems to appear.

Specifically, though it's a 64-bit type, it does not have the necessary encoding to provide 264 distinct floating point values. There are many bit patterns that map to the same value.

For example, NaN is encoded as the exponent field of binary 1111 1111 with non-zero fraction (23 bits) regardless of the sign (one bit). That's 2 * (223 - 1) (over 16 million) distinct values representing NaN.

So, yes, your "due to how floats are handled and rounded" comment is correct.

In terms of fixing it, you'll either have to limit your strings to values that can be represented by doubles exactly, or find a way to send the strings across the CAN bus.

For example (if you can't send strings), two 32-bit integers could represent an 8-character string value with zero chance of information loss.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    More specifically, a double precision value has a precision of 53 *bits*, which clearly will not represent 7 bytes of raw data. – Karl Knechtel Oct 24 '22 at 23:18