First,
While it was pointed out earlier to do the math in 32 bits, but it was not clearly explained. For someone who doesn't yet understand int vs float for (x*5/8), it may be beneficial to explained:
The multiply is done first to preserve as much precision as feasible, but multiply int16 with another number could overflow the int16. So the multiply should be done in 32 bit.
Second,
The following is perhaps easier to understand than shifting and can be generalized. The approach is to do the multiply first (to preserve the precision):
int y=something;
int x = ((y*5L)/8L); // force to do the multiply first so precision is preserved but not rounded
In this version, fraction is preserved until the last divide. The last divide truncates the fraction.
Third, rounding (for positive)
int y=something;
int x = (((y*10L*5L)/8L+5L)/10L;
You can see that the factor is 10*5/8 instead of 5/8. For rounding, we normally add a 0.5. But we are doing it in integer, so 0.5 is not feasible. To get around that, multiply the expression by 10 first, now we can add a 5 to round it to the tens and divide it back down by 10.
Fourth, rounding (for positive or negative)
int y=something;
int x = (((y*10L*5L)/8L+(y<0 ? -5L : 5L))/10L;
You can see the same 10*5/8. But instead of just adding 5L, it does a test first:It adds -5 if y is negative, otherwise it adds +5.
Fifth, something for you to try
The code (and output):
int y1=24094; // a number that overflows on multiply and needs rounding
int y2=-y1; // same number in negative
int result1,result2,result3,result4,result5,result6,result7;
Serial.print(" y1=");Serial.println(y1);
Serial.print(" y2=");Serial.println(y2);
Serial.println("Result when done in float expression:");
Serial.print(" y1*5.0/8.0=");Serial.println((float) y1*5.0/8.0);
Serial.print(" y2*5.0/8.0=");Serial.println((float) y2*5.0/8.0);
Serial.println();
Serial.print(" R1 forcing eval constant first=");Serial.println( result1 = (y1 * ( 5 / 8 )) );
Serial.print(" R2 multiply in int16 first cause overflow=");Serial.println( result2 = (y1 * 5) / 8 );
Serial.print(" R3 multiply in int32 eliminiated overflow=");Serial.println( result3 = ((y1 * 5L) / 8L) );
Serial.println();
Serial.print(" R4 +rounding a + =");Serial.println( result4 = ((y1*10L*5L)/8L + 5L)/10L );
Serial.print(" R5 +rounding a - =");Serial.println( result5 = ((y2*10L*5L)/8L + 5L)/10L );
Serial.print(" R6 +-rounding a + =");Serial.println( result6 = ((y1*10L*5L)/8L + (y1<0 ? -5L : 5L))/10L );
Serial.print(" R7 +-rounding a - =");Serial.println( result7 = ((y2*10L*5L)/8L + (y2<0 ? -5L : 5L))/10L );
You should see that result6 and result7 give you the same results for + and - with rounding. Complicated as that expression looks, it is still many times faster than doing it in float. This is what you should see with the serial output:
y1=24094
y2=-24094
Result when done in float expression:
y1*5.0/8.0=15058.75
y2*5.0/8.0=-15058.75
R1 forcing eval constant first=0
R2 multiply in int16 first cause overflow=-1325
R3 multiply in int32 eliminiated overflow=15058
R4 +rounding a + =15059
R5 +rounding a - =-15058
R6 +-rounding a + =15059
R7 +-rounding a - =-15059
Hope this helps in your understanding and future code development...
Rick