Sure. And you're right, MK14; I didn't mean to imply it applies here, just wanted to describe where it comes from.
Let's delve into the math a bit, though, because even the integer form of the Arduino map() gets this stuff wrong (it does not yield the same results as the float form one does!).
When you want to convert \$V\$ to \$R\$, with \$V_\min \le V \le V_\max\$, and \$R = R_0\$ when \$V = V_\min\$ and \$R = R_1\$ when \$V = V_\max\$, the exact mathematical formula is
$$R = R_0 + \left(V - V_\min\right) \frac{R_1 - R_0}{V_\max - V_\min}$$
When we use integers and integer arithmetic (\$V, R \in \mathbb{Z}\$), the situation is somewhat more interesting, especially when uses SAR ADCs, because then \$V_\min \le V \lt V_\max\$, and \$V_\max = V_\min + 2^N\$. In other words, \$V_\max\$ is exclusive. Nothing else in the formula changes, though. To apply correct rounding, i.e.
$$R = R_0 + \left\lfloor \left(V - V_\min\right) \frac{R_1 - R_0}{V_\max - V_\min} \right\rceil$$
we do
$$R = R_0 + \left\lfloor \frac{ (V - V_\min) (R_1 - R_0) + C}{V_\max - V_\min} \right\rceil, \quad C = \operatorname{sgn}(R_1 - R_0) \frac{V_\max - V_\min}{2}$$
i.e. \$C\$ is half the divisor with the same sign as \$R_1 - R_0\$, and picking \$\lfloor \rfloor\$ when \$C\$ is positive, and \$\lceil \rceil\$ when \$C\$ is negative.
In C, we can write this using correct rounding as for example
int32_t map(const int32_t in, const int32_t inmin, const int32_t inlimit, const int32_t outmin, const int32_t outlimit)
{
const uint32_t i = (in >= inmin) ? in - inmin : 0;
const uint32_t im = (inlimit > inmin) ? inlimit - inmin : 1;
if (outlimit > outmin) {
const uint32_t om = outlimit - outmin;
return outmin + (int32_t)((i * om + (im >> 1)) / im);
} else
if (outmin > outlimit) {
const uint32_t om = outmin - outlimit;
return outmin - (int32_t)((i * om + (im >> 1)) / im);
} else
return outmin;
}
When
outlimit - outmin is a power of two, the division simplifies to a bit shift. The above only works correctly when
im*om ≤ 232, and
inmin ≤ in < inlimit. Then, if
outmin < outlimit,
outmin ≤ result < outlimit. If
outmin > outlimit, then
outlimit < result ≤ outmin, and
result is correctly rounded, halfway towards
outlimit. However, if
im ≥ 2 om, i.e. the output range is half of the input range or less, then
result can reach
outlimit due to rounding. When the output range is smaller than the input range, I recommend omitting the rounding. Whenever output range is more than half the input range,
result won't reach
outlimit unless
in ≥ inlimit.
(We could do the above for
short,
int and
long types in Arduino, using
__builtin_clz() or
__builtin_clzl() to find the size of
im and
om in bits and using a larger cast (
long or
long long) when necessary, getting fast but accurate integer
map() whose behaviour matches that of the one with
float parameters. Details like this bug me, because they are often the cause of weird behavioural differences due to tiny changes in the code that should not affect arithmetic results that much.)
When
inmin,
inlimit,
outmin, and
outlimit are fixed, it makes sense to precalculate
om and
im, and divide both with their greatest common denominators (which for a power of two
om means shifting both right until
im becomes odd).
For the 0,4096 → 0,10000 conversion (assuming exclusive limits, like on SAR ADCs), 10000/4096 = 625/256, exactly, and the result of the multiplication fits in 22 bits. The function is then simply
uint_fast16_t adc_to_millivolts(uint_fast16_t i) { return ((uint_fast32_t)i * 625 + 128) >> 8); }
and
adc_to_millivolts(4095) == 9998 , as expected. (40950000/4096 = 9997.55859375, which rounds to 9998.)
In fact, this routine returns the exact same results as
(uint_fast16_t)floor(0.5 + i * 10000.0 / 4096.0); and, if using round away from zero, with (uint_fast16_t)round(i * 10000.0 / 4096.0). The latter differs from the former for
i=128,640,1152,1664,2176,2688,3200,3712 because the standard rule for IEEE 754 floating-point math is to round halfway towards odd, not away from zero, and these values of
i are at exact half-millivolt boundaries.
With inclusive limits 0,4095 → 0,10000 conversion requires a division by 4095, or a fractional multiplication by 10000/4095 = 2000/819 ≃ 2.44200
244200... which we can approximate with 160039/65536, compensating a bit with the rounding constant (32951 instead of 32768):
uint_fast16_t adc_to_millivolts(uint_fast16_t i) { return ((uint_fast32_t)i * 160039 + 32951) >> 16; }
which yields 169 instead of 168 for i=69; 1390 instead of 1389 for i=569; 3390 instead of 3389 for i=1388, and 8610 instead of 8611 for i=3526; and the exact same results as
(uint_fast16_t)floor(0.5 + i * 10000.0 / 4095.0) for the rest of i=0..4095, inclusive.