This is a further discussion in the vein of 3v0's scaling article posted earlier.
Percentage calculations become necessary reasonably frequently, as a correction factor for sensors or for scaling numbers to fit on a limited digit display or any number of applications.
Recently, I had reason to scale variable 8 bit numbers by a variable percentage in ASM. I also had a limitation of running the microcontroller at a low clock speed to be power efficient as a solar application.
The key in maintaining reasonable accuracy is in using longer integers (2, 3 or 4byte. etc.) to do the calculations before finally dividing down to the result.
For speed of calculation purposes I kept the scaling down to a 2 byte integer which also makes this discussion easier to implement.
Eg. Correction percentage (calibration) factor to make 88 (an ADC reading) into 108.
Actual % correction => (108-88)/88 = 20/88 = 22.7%
A 16 bit integer can contain the product of 2, 8 bit integers, eg. 256*256 = 65536 values including 0, in most 8 bit calcs we tend to regard 255 as the max value held in an 8 bit integer as 0 is the zeroth value and 255 is the 256th value. Therefore for our purposes the max 16 bit integer value will be 255 * 255 = 65025 or about 2 ^15.99.
We could attempt 'normal' 16 bit arithmetic by doing 88 + (20 * 88) /100 = 88 + 17 = 105, an integer error of about 3%.
However, the division by 100 is problematic in terms of code and time efficiency and accuracy may be a problem.
Exploiting binary arithmetic & byte value capacity is better:
;_________________________________________________________;
As a prelude I point out that to multiply by 2.5 we multiply the original by 2 (shift bits left by 1) and add 1/2 the original value ( 1/2 = shift bits right by 1).
Dividing a 16 bit (2 byte) integer by 256 simply takes the hi byte as the result. No 'calcs' needed.
Multiplying by 33 requires a shift of 5 bits to the left and adding the original. Dividing by 32 (2^5) is a shift of 5 bits to the right.
;________________________________________________________;
Using integer asm and binary shifts we can do:
88+(20*88* 2.5)/256 = 105, with a similar 3% error. But much faster 8 bit division and acceptable noting that the numerator multiplication should use a 2 byte(16 bit result).
If required for better than 3% accuracy we can also use integer math to 'correct' for the 3% error by 105 * 33/32 = 108.
Thus accurate % calculations can be simply and efficiently done using pure 16 bit integer asm. This approach can be expanded to multi-byte integers for even greater precision.
Percentage calculations become necessary reasonably frequently, as a correction factor for sensors or for scaling numbers to fit on a limited digit display or any number of applications.
Recently, I had reason to scale variable 8 bit numbers by a variable percentage in ASM. I also had a limitation of running the microcontroller at a low clock speed to be power efficient as a solar application.
The key in maintaining reasonable accuracy is in using longer integers (2, 3 or 4byte. etc.) to do the calculations before finally dividing down to the result.
For speed of calculation purposes I kept the scaling down to a 2 byte integer which also makes this discussion easier to implement.
Eg. Correction percentage (calibration) factor to make 88 (an ADC reading) into 108.
Actual % correction => (108-88)/88 = 20/88 = 22.7%
A 16 bit integer can contain the product of 2, 8 bit integers, eg. 256*256 = 65536 values including 0, in most 8 bit calcs we tend to regard 255 as the max value held in an 8 bit integer as 0 is the zeroth value and 255 is the 256th value. Therefore for our purposes the max 16 bit integer value will be 255 * 255 = 65025 or about 2 ^15.99.
We could attempt 'normal' 16 bit arithmetic by doing 88 + (20 * 88) /100 = 88 + 17 = 105, an integer error of about 3%.
However, the division by 100 is problematic in terms of code and time efficiency and accuracy may be a problem.
Exploiting binary arithmetic & byte value capacity is better:
;_________________________________________________________;
As a prelude I point out that to multiply by 2.5 we multiply the original by 2 (shift bits left by 1) and add 1/2 the original value ( 1/2 = shift bits right by 1).
Dividing a 16 bit (2 byte) integer by 256 simply takes the hi byte as the result. No 'calcs' needed.
Multiplying by 33 requires a shift of 5 bits to the left and adding the original. Dividing by 32 (2^5) is a shift of 5 bits to the right.
;________________________________________________________;
Using integer asm and binary shifts we can do:
88+(20*88* 2.5)/256 = 105, with a similar 3% error. But much faster 8 bit division and acceptable noting that the numerator multiplication should use a 2 byte(16 bit result).
If required for better than 3% accuracy we can also use integer math to 'correct' for the 3% error by 105 * 33/32 = 108.
Thus accurate % calculations can be simply and efficiently done using pure 16 bit integer asm. This approach can be expanded to multi-byte integers for even greater precision.