Why is division by 4096 not the same as ">>12"?

C++ reference under Built-in bitwise shift operators states
For negative a, the value of a >> b is implementation-defined (in most implementations, this performs arithmetic right shift, so that the result remains negative).
i.e. in general a >> b is an arithmetic shift (a copy of the sign bit is shifted in) but can be logical shift (0 is shifted in)
this can cause problems when porting software - a few years ago a colleague of mine porting a large scheduling package onto to a new computer found his tests failed due to difference in >> implementation

Java overcomes the problem by have two right shift operators
signed right shift >> preserves the leftmost bit (sign bit)
unsigned right shift >>> shifts a zero into the leftmost position

Sorry, my wording was unclear. I was refering to the size of the compiled code, i.e. what is loaded into the UNO R3, not the file size of the *.ino (which is meaningless for sure).
But after playing with the code it seems that this value, at the end, is not telling anything.

Hi,

My bad, I thought that my example is about positive numbers, but as written in #11, division is done with negative numbers within my provided example. I trapped into my own trap.

Code with both negative and positive numbers being shifted/divided:

void setup() {
  Serial.begin(115200);
  delay(1000);
  int   m_a   =  3934;
  long  uScal = 64000;
  long  m_xShift = 0;
  long  m_xDiv   = 0;
  long  m_xNextShift, m_xNextDiv,factorShift, factorDiv;
  int   delta = 0;
  for (int a = 1; a >= -1; a = a-2) {
    uScal = a * uScal;
    m_xShift = 0;
    m_xDiv   = 0;
    Serial.print("uScal = "); Serial.println(uScal);
    Serial.println("n\tm_xNextShift\tm_xNextDiv\tdelta\tfactorShift\tfactorDiv");
    for(byte n = 0; n <= 5; n++) {
      // Shift
      m_xNextShift  = (((long)m_a * (m_xShift - uScal)) >>  12 ) + uScal; 
      m_xShift      = m_xNextShift;
      factorShift   = m_xShift - uScal;
      // Div
      m_xNextDiv    = (((long)m_a * (m_xDiv   - uScal)) / 4096L) + uScal;
      m_xDiv        = m_xNextDiv;
      factorDiv     = m_xDiv   - uScal;
      // Difference in result
      delta = m_xNextShift - m_xNextDiv;
      Serial.print(n);            Serial.print("\t");
      Serial.print(m_xNextShift); Serial.print("\t\t");
      Serial.print(m_xNextDiv);   Serial.print("\t\t");
      Serial.print(delta);        Serial.print("\t");
      Serial.print(factorShift);  Serial.print("\t\t");
      Serial.println(factorDiv);
    }
    Serial.println("=====");
  }
  Serial.println(" ");
  
}

void loop() {
  while(1);
}

which gives (note that m_a is positive, so factor* is the sign the division is taken.

uScal = 64000
n	m_xNextShift	m_xNextDiv	delta	factorShift	factorDiv
0	2531			2532		-1		-61469		-61468
1	4962			4964		-2		-59038		-59036
2	7296			7299		-3		-56704		-56701
3	9538			9542		-4		-54462		-54458
4	11692			11696		-4		-52308		-52304
5	13760			13765		-5		-50240		-50235
=====
uScal = -64000
n	m_xNextShift	m_xNextDiv	delta	factorShift	factorDiv
0	-2532			-2532		0		61468		61468
1	-4964			-4964		0		59036		59036
2	-7299			-7299		0		56701		56701
3	-9542			-9542		0		54458		54458
4	-11696			-11696		0		52304		52304
5	-13765			-13765		0		50235		50235
=====

So, shift and division are the same for positive numbers. But differs as outlined by the posts above for negative numbers (rounding "downwards" vs truncating (rounding "upwards" ).

I guess, this is the problem: you never know (except for java, as you have pointed out). So, I think, keeping the division and multiplication is, in general the more defense programming approach - knowing that the compiler most likely will detect division of 2^n and then does the right optimization.

Edit1:

Interesting link!

Edit2:
And now I don't know where to tick the solution box (so I took one naming the possible conflict of what is coming in from the left (0 or 1)).
@jremington, @xfpd , @david_2018 , @alto777 , @bobcousins : Thanks!

I think it looks like truncating, it's just what means truncation. I wrote a sketch just using int and division by 8.

There is no evidence of mathematical rounding, that is to say once you get closer to the other choice being more accurate, both shifting and integer division persist in returning the same integer.

As you note, for positive values all is agreed. For negative values in between where 8 divides evenly, division moves to the greater integer and shifting to the lesser integer.

What I mean.
y >> 3 y >> 3 y / 8 y / 8.0
-24 -3 -3 -3
-23 -3 -2 -2.88
-22 -3 -2 -2.75
-21 -3 -2 -2.63
-20 -3 -2 -2.5
-19 -3 -2 -2.38
-18 -3 -2 -2.25
-17 -3 -2 -2.13
-16 -2 -2 -2
-15 -2 -1 -1.88
-14 -2 -1 -1.75
-13 -2 -1 -1.63
-12 -2 -1 -1.5
-11 -2 -1 -1.37
-10 -2 -1 -1.25
-9 -2 -1 -1.12
-8 -1 -1 -1
-7 -1 0 -0.88
-6 -1 0 -0.75
-5 -1 0 -0.63
-4 -1 0 -0.5
-3 -1 0 -0.37
-2 -1 0 -0.25
-1 -1 0 -0.12
0 0 0 0
1 0 0 0.12
2 0 0 0.25
3 0 0 0.37
4 0 0 0.5
5 0 0 0.63
6 0 0 0.75
7 0 0 0.88
8 1 1 1
9 1 1 1.12
10 1 1 1.25
11 1 1 1.37
12 1 1 1.5
13 1 1 1.63
14 1 1 1.75
15 1 1 1.88
16 2 2 2
17 2 2 2.13
18 2 2 2.25
19 2 2 2.38
20 2 2 2.5
21 2 2 2.63
22 2 2 2.75
23 2 2 2.88

The sketch.
void setup() {
  Serial.begin(115200);

  int y;

  Serial.print("y >> 3\t");
  Serial.print("y >> 3\t");
  Serial.print("y / 8\t");
  Serial.print("y / 8.0\t");
  Serial.println("");

  for (y = -24; y < 24; y++) {

    int a = y >> 3;
    int b = y / 8;
    float m = y / 8.0;

    Serial.print(y); Serial.print("\t");
    Serial.print(a); Serial.print("\t");
    Serial.print(b); Serial.print("\t");
    Serial.print(m);

    Serial.println("");
  }

}

void loop() {
}

I came across references to ones-complement maths and quit. The extent to which any of this is implementation-dependent and further the extent to which such implementations don't all do a common thing remains unknown. To me.

Caveat Programmator.

a7

which shown by the example of @alto777 above (I think you mixed something in your wording, but the example is showing it). And shift always round downward (-1,63 becomes -2, whereas +1.63 becomes 1)

... same for me :grin: