I am writing firmware for a microcontroller. (In this specific case it is a Microchip PIC, but I am looking for an answer that is equally applicable to any brand of microcontroller.)
Arduino has a map function to convert a value from one range to another...
long map(long x, long in_min, long in_max, long out_min, long out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
...but the internet is full of people claiming it is very imprecise due to it not using floating-point arithmetic. My math skills are poor-to-moderate, so I will just take their word for it.
I believe there ought to be a better solution, without resorting to "expensive" floating-point math. For example, where the inputs are all 8-bit integers (uint8_t), I have had reasonable success simply casting the values to uint16_t and multiplying them by some factor (such as 10 or 100) before performing a mapping similar to that shown above. In this way, we can simulate one or two fixed-point fractional places without using floating-point arithmetic, significantly improving precision. (Unless I'm just grossly misunderstanding the situation, which is certainly possible.)
I suspect that my solution is also fairly naive and inefficient, so I want to see if smarter people can come up with a better way.
My requirements are:
- Written in C (not C++!)
- Can handle
uint8_tanduint16_tvalues (either using templates, or by having two different versions of the function). - Able to map any non-negative integer value from any range of the same type to any other range of the same type, with perfect accuracy.
- Does not use
floatordoubledata types. - Microcontrollers often have very small memories. The code size and the RAM usage of the function must be kept small. (How small? I'm not sure. But for reference, the chip I'm using today has space for 2048 assembly code instructions and 512 bytes of RAM, so a good solution should probably not use more than about 10% of that; ideally less.)
- The computation should not be so time-inefficient that it impacts performance. (Assume the chip can perform 1 million assembly instructions per second, and the computation shouldn't take more than a 20th of that.)
Example:
uint8_t input = 162;
uint8_t in_min = 12, in_max = 233, out_min = 5, out_max = 199;
uint8_t output = map_uint8(input, in_min, in_max, out_min, out_max);
// output == 137
(in_max - in_min)/2after multiplication.