Strange behaviour with bit shifting

Hello,

I'm am building a wireless weather station using the Bosch BME280 sensor :slight_smile:

Instead of using the standard library I'll rather code the entire thing by myself using the compensation formula provided in the datasheet.

This expression is given in the datasheet:

var2 = (((P >> 2) * dig_P8) >> 13;

Written as is it gives me an unexpected result (gives 493779, with uint32_t P = 97724 and int32_t dig_P8 = -10230 )

But splitting the expression works well (gives -30509)

var2 = (((P >> 2) * dig_P8);
var2 >>= 13;

Why?

Why?

Because of 16 bit signed integer arithmetic, probably.
We'd know for sure if you'd posted your code.

Here it is, since I'm a beginner the code is messy :confused:

The interesting part is a few lines above the end of the function "compute_pressure"

#include <Wire.h>

#define BME280_address 0x77

uint16_t            dig_T1;
signed short int    dig_T2;
signed short int    dig_T3;
uint16_t            dig_P1;
int32_t             dig_P2;
int32_t             dig_P3;
int32_t             dig_P4;
int32_t             dig_P5;
int32_t             dig_P6;
int32_t             dig_P7;
int32_t             dig_P8;
int32_t             dig_P9;
unsigned char       dig_H1;
signed short int    dig_H2;
unsigned char       dig_H3;
signed short        dig_H4;
signed short        dig_H5;
signed char         dig_H6;
long signed int     t_fine;
uint32_t            raw_P;
byte                BME280_calib [31] = {};



void setup() {

Wire.begin(); 
Serial.begin(9600);
   
Wire.beginTransmission(0x77);
Wire.write(0x88);
Wire.endTransmission();
Wire.requestFrom(BME280_address,25);
for (int i=0; i <25; i++)
  {
    BME280_calib[i] = Wire.read();
  }
Wire.beginTransmission(0x77);
Wire.write(0xE1);
Wire.endTransmission();
Wire.requestFrom(BME280_address,3);
for (int i=25; i <31; i++)
  {
    BME280_calib[i] = Wire.read();
  }

  dig_T1 = BME280_calib [1] << 8 | BME280_calib [0];
  dig_T2 = BME280_calib [3] << 8 | BME280_calib [2];
  dig_T3 = BME280_calib [5] << 8 | BME280_calib [4];
  dig_P1 = BME280_calib [7] << 8 | BME280_calib [6];
  dig_P2 = BME280_calib [9] << 8 | BME280_calib [8];
  dig_P3 = BME280_calib [11]<< 8 | BME280_calib [10];
  dig_P4 = BME280_calib [13]<< 8 | BME280_calib [12];
  dig_P5 = BME280_calib [15]<< 8 | BME280_calib [14];
  dig_P6 = BME280_calib [17]<< 8 | BME280_calib [16];
  dig_P7 = BME280_calib [19]<< 8 | BME280_calib [18];
  dig_P8 = BME280_calib [21]<< 8 | BME280_calib [20];
  dig_P9 = BME280_calib [23]<< 8 | BME280_calib [22];
  dig_H1 = BME280_calib [24];
  dig_H2 = BME280_calib [26]<< 8 | BME280_calib [25];
  dig_H3 = BME280_calib [27];
  dig_H4 = BME280_calib [28];
  dig_H5 = BME280_calib [29];
  dig_H6 = BME280_calib [30];
/*
Serial.print("dig_P1 = ");
Serial.println (dig_P1);
Serial.print("dig_P2 = ");
Serial.println (dig_P2);
Serial.print("dig_P3 = ");
Serial.println (dig_P3);
Serial.print("dig_P4 = ");
Serial.println (dig_P4);
Serial.print("dig_P5 = ");
Serial.println (dig_P5);
Serial.print("dig_P6 = ");
Serial.println (dig_P6);
Serial.print("dig_P7 = ");
Serial.println (dig_P7);
Serial.print("dig_P8 = ");
Serial.println (dig_P8);
Serial.print("dig_P9 = ");
Serial.println (dig_P9);
*/
}

void loop() {



//get raw temperature:
uint32_t raw_T;
Wire.beginTransmission(0x77);
Wire.write(0xF4);
Wire.write (0x25);
Wire.endTransmission();

Wire.beginTransmission(0x77);
Wire.write(0xFA);
Wire.endTransmission();
Wire.requestFrom(BME280_address,3);
raw_T = Wire.read();
raw_T <<= 8;
raw_T = raw_T | Wire.read();
raw_T <<= 8;
raw_T = raw_T | Wire.read();
raw_T >>= 4;


//compute temperature:
int32_t var1, var2, poub1, poub2;
float T;
//raw_T = raw_T >> 4;
//var1  = ((raw_T>>3 -dig_T1<<1) * dig_T2) >> 11;
poub1 = raw_T >> 3;
poub2 = dig_T1<<1;
var1 = poub1 - poub2;
var1 = var1 * dig_T2;
var1 = var1 >> 11;
//var2  = ((((raw_T>>4 -dig_T1) * (raw_T>>4 -dig_T1)) >> 12) * dig_T3) >> 14;/*
poub1 = raw_T >> 4;
poub1 = poub1 - dig_T1;
poub1 = poub1 * poub1;
poub1 = poub1 >> 12;
poub1 = poub1 * dig_T3;
var2 = poub1 >>14;


t_fine = var1 + var2;
T  = (t_fine * 5 + 128) >> 8;
T= T/100;


Serial.print ("T= ");
Serial.print (T);
Serial.println ("degC");

//get raw pressure:
Wire.beginTransmission(0x77);
Wire.write(0xF7);
Wire.endTransmission();
Wire.requestFrom(BME280_address,3);
raw_P = Wire.read();
raw_P <<= 8;
raw_P = raw_P | Wire.read();
raw_P <<= 8;
raw_P = raw_P | Wire.read();
raw_P >>= 4;
compute_pressure();

delay (5000);
}



void compute_pressure(){
  
#define alt 290.0
int32_t var1, var2;
uint32_t P;
float P_atm, var3;

//formules de compensation datasheet BME280:
var1 = (t_fine >> 1) - 64000;
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * dig_P6;
var2 = var2 + ((var1 * dig_P5) << 1);
var2 = (var2 >>2) + (dig_P4 << 16);
var1 = (((dig_P3 * ((var1 >> 2) * (var1 >> 2)) >> 13) >> 3) + ((dig_P2 * var1) >> 1)) >> 18;
var1 = ((var1 + 32768) * dig_P1) >> 15;
if (var1 == 0)
  {}
  
P = ((1048576 - raw_P) - (var2 >> 12)) * 3125;

if (P < 0x80000000)
{
  P = (P << 1) / var1;
}
else    //JAMAIS TESTE  
{
  P = (P / var1) * 2;
}

var1 = (dig_P9 * ((P >> 3) * (P >> 3) >> 13)) >> 12;
var2 = ((P >> 2) * dig_P8);
var2 >>= 13;

P = P + ((var1 + var2 + dig_P7) >> 4);

P_atm = P;
P_atm /= 100;

//pression au niveau de la mer, formule datasheet BMP180:
var3 = 1 - (alt / 44330);
var3 = pow (var3,5.255);
P_atm = P_atm / var3;
Serial.print ("P_atm = ");
Serial.print (P_atm);
Serial.println ("hPa");
}

Would be easier to debug if all the right shifts (>>11) were replaced by divisions (/ 2048) then put the right shifts back after its working OK.

outsider:
Would be easier to debug if all the right shifts (>>11) were replaced by divisions (/ 2048) then put the right shifts back after its working OK.

No. The compiler will optimize a /2048 to a shift anyway. But yes, it would make it easier to read if you left it instead of putting it back. :slight_smile:

Is there a difference between right shifting an unsigned quantity vs. a signed quantity? Because the expression ((P >> 2) * dig_P8) may have been evaluated to an unsigned because P is unsigned. But var2 is signed. When you separated it you made the >>13 signed, since var2 is signed.

I only ask, because the firewall has deprived me of any chance of research.

aarg:
No. The compiler will optimize a /2048 to a shift anyway. But yes, it would make it easier to read if you left it instead of putting it back. :slight_smile:

int x=10000;

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:
x = x>>1;
Serial.println(x);
}

1946 bytes compiled size

int x=10000;

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:
x = x/2;
Serial.println(x);
}

1952 bytes compiled size.

So it looks like the compiler isn't as smart as we might expect.... (this shocked me when I came across it too - I also expected it to be optimized out)

DrAzzy:
So it looks like the compiler isn't as smart as we might expect.... (this shocked me when I came across it too - I also expected it to be optimized out)

The compiler is probably smart enough to figure out you can't substitute bit shifting as division on signed ints I'm guessing

DrAzzy:
So it looks like the compiler isn't as smart as we might expect....

x = x >> 1;

000000c0 <loop>:
  c0:	60 91 00 01 	lds	r22, 0x0100
  c4:	70 91 01 01 	lds	r23, 0x0101
  c8:	75 95       	asr	r23
  ca:	67 95       	ror	r22
  cc:	70 93 01 01 	sts	0x0101, r23
  d0:	60 93 00 01 	sts	0x0100, r22
  d4:	4a e0       	ldi	r20, 0x0A	; 10
  d6:	50 e0       	ldi	r21, 0x00	; 0
  d8:	84 e2       	ldi	r24, 0x24	; 36
  da:	91 e0       	ldi	r25, 0x01	; 1
  dc:	0c 94 11 02 	jmp	0x422	; 0x422 <_ZN5Print7printlnEii>

x = x / 2;

000000c0 <loop>:
  c0:	60 91 00 01 	lds	r22, 0x0100
  c4:	70 91 01 01 	lds	r23, 0x0101
  c8:	77 ff       	sbrs	r23, 7
  ca:	02 c0       	rjmp	.+4      	; 0xd0 <loop+0x10>
  cc:	6f 5f       	subi	r22, 0xFF	; 255
  ce:	7f 4f       	sbci	r23, 0xFF	; 255
  d0:	75 95       	asr	r23
  d2:	67 95       	ror	r22
  d4:	70 93 01 01 	sts	0x0101, r23
  d8:	60 93 00 01 	sts	0x0100, r22
  dc:	4a e0       	ldi	r20, 0x0A	; 10
  de:	50 e0       	ldi	r21, 0x00	; 0
  e0:	84 e2       	ldi	r24, 0x24	; 36
  e2:	91 e0       	ldi	r25, 0x01	; 1
  e4:	0c 94 15 02 	jmp	0x42a	; 0x42a <_ZN5Print7printlnEii>