Random high byte when double converted to signed 16 int and sent over BLE

I am taking over a firmware Bluetooth project and am reading the code that is having some issues. Below is a snippet from a BLE characteristic read callback.  Here a double is being converted to a 16 bit int. I am not sure why it is converted to a long first, but anyway the problem is that there seems to be a random higher byte when it should be 0

For example, when force is equal to 250, I would expect the value seen in nrfConnect to be 0x00-FA, but the value received has the first byte with a value that makes no sense and it changes.

For example I receive 0x34-FA, 0x68-FA, etc. FA is 250 so that is correct, but the higher byte is seemingly random.

The same goes for when force is 0, nrfConnect shows

0x22-00, 0x10-00, and so on with a random higher byte when I would expect 0x00-00



//read force levels
double force = read_force();

//convert to 16 bit int
long val_long = (long) force;
int16_t val16 = (int16_t) val_long;
int16_t *val16_ptr = &val16;

//dispatch value
return bt_gatt_attr_read(conn, attr, buf, len, offset, val16_ptr,
sizeof(*val16_ptr));

  • A double is 8 bytes and a int16_t is 2 bytes. 

    If you cast a double to a int16_t you are going to truncate the decimal value.

    double d = 250.55;
    int16_t i = (int16_t)d; //i = 250

    If you are casting a `double*` to a `*int16_t*` and tying to dereference, you are going to get undefined behavior because its going to read 8 bytes from something that only has 2 bytes, so the value could be anything. You are probably doing something similar to this if your value changes on different runs of the application.

    int16_t i = 250;
    double d = *(double*)&i; // Undefine behavior. The value of i could be anything

    If you go the other way and cast a `*int16_t*` to a double*` and dereference, you will get a seemingly nonsensical value but it should stay the same between runs. This is because the memory layout of int_16 and double are not the same and you are reading only 2 bytes of the double.

    double d = 250.55;
    int16_t i = *(int16_t*)&d;

  • Hi.  I don't understand this scenario: "If you are casting a `double*` to a `*int16_t*` and tying to dereference, you are going to get undefined behavior because its going to read 8 bytes from something that only has 2 bytes, so the value could be anything."

    My code is pasted in the original post.  I don't dereference a pointer at any time.  bt_gatt_attr_read takes in a pointer and the number of bytes (2).  It's not reading more than 2 bytes, but the high byte that is read is seemingly random.

  • Some more context would be helpful. Can you show the rest of the function?

    `val16` seems to be the value you are reading, but you are not returning it from the function.

    What variable are you looking at that contains the bad value?

    If your function looks like this 

    static int read_value() {
    	double force = read_force();
    
    	long val_long = (long) force;
    	int16_t val16 = (int16_t) val_long;
    	int16_t *val16_ptr = &val16;
    
    	return bt_gatt_attr_read(conn,
    		attr,
    		buf,
    		len,
    		offset,
    		val16_ptr,
    		sizeof(*val16_ptr));
    
    }

    Then the value read from the attribute is `val16` and only stored in a local value, so that value will never be usable outside this function.

    Also im not understanding why `val16` is being assigned to anything if its value is gong to be overwritten with the attribute's value.

  • Hi thanks for helping.  This is the read characteristic callback function for BLE.  How would you recommend converting a double to an int16?


    //read force characteristic
    static ssize_t read_force_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
        void *buf, uint16_t len, uint16_t offset) {
      double force = read_force();
      long val_long = (long) force;
      int16_t val16 = (int16_t) val_long;
    
      int16_t *val16_ptr = &val16;
      
      printk("read_force callback %d\n", *val16_ptr);
      return bt_gatt_attr_read(conn, attr, buf, len, offset, val16_ptr,
          sizeof(*val16_ptr));
    }

  • Try this

    static ssize_t read_force_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) {
    	int16_t force = read_force();
    	printk("read_force callback. Value: %i\n", force);
    	return bt_gatt_attr_read(conn, attr, buf, len, offset, &force, sizeof(force));
    }

Related