Created
July 19, 2023 21:24
-
-
Save d-a-v/4e1cfe2448aaa4a9ded8f05599cf3f20 to your computer and use it in GitHub Desktop.
arduino map() tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// g++ -Ofast -march=native -Wall -Wextra testmap.cc -o testmap && time ./testmap | |
#include <iostream> | |
#include <cmath> | |
#include <sys/time.h> | |
long map_test(long x, long in_min, long in_max, long out_min, long out_max) { | |
long in_length = in_max - in_min; | |
long out_length = out_max - out_min; | |
if (in_length == 0 || out_length == 0) { | |
return out_min; | |
} | |
long delta = x - in_min; | |
if ((out_length < 0) && (in_length < 0)) { | |
std::swap(in_min, in_max); | |
std::swap(out_min, out_max); | |
} else if (out_length < 0) { | |
x = in_max - delta; | |
std::swap(out_min, out_max); | |
} else if (in_length < 0) { | |
x = in_max - delta; | |
std::swap(in_min, in_max); | |
} | |
// Update length and delta as in/out values may have changed. | |
delta = x - in_min; | |
in_length = in_max - in_min; | |
out_length = out_max - out_min; | |
if (out_length == in_length) { | |
return out_min + delta; | |
} | |
// We now know in_min < in_max and out_min < out_max | |
// Make sure x is within range of in_min ... in_max | |
// Shift the in/out range to contain 'x' | |
if ((x < in_min) || (x > in_max)) { | |
long shift_factor = 0; | |
if (x < in_min) { | |
const long before_min = in_min - x; | |
shift_factor = -1 - (before_min / in_length); | |
} else { | |
// x > in_max | |
const long passed_max = x - in_max; | |
shift_factor = 1 + (passed_max / in_length); | |
} | |
const long in_shift = shift_factor * in_length; | |
const long out_shift = shift_factor * out_length; | |
in_min += in_shift; | |
in_max += in_shift; | |
out_min += out_shift; | |
out_max += out_shift; | |
delta = x - in_min; | |
} | |
if (out_length > in_length) { | |
// Map to larger range | |
// Do not 'simplify' this calculation | |
// as this will result in integer overflow errors | |
const long factor = out_length / in_length; | |
const long error_mod = out_length % in_length; | |
const long error = (delta * error_mod) / in_length; | |
return (delta * factor) + out_min + error; | |
} | |
// abs(out_length) < abs(in_length) | |
// Map to smaller range | |
// Do not 'simplify' this calculation | |
// as this will result in integer overflow errors | |
const long factor = (in_length / out_length); | |
const long estimate_full = in_length / factor + out_min; | |
const long error = (delta * (out_max - estimate_full)) / in_length; | |
return delta / factor + out_min + error; | |
} | |
long map_arduino(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; | |
} | |
long map_esp8266(long x, long in_min, long in_max, long out_min, long out_max) { | |
const long dividend = out_max - out_min; | |
const long divisor = in_max - in_min; | |
const long delta = x - in_min; | |
return (delta * dividend + (divisor / 2)) / divisor + out_min; | |
} | |
long test_map(long x, long in_min, long in_max, long out_min, long out_max) { | |
const double out_length = out_max - out_min; | |
const double in_length = in_max - in_min; | |
if (in_length == 0 || out_length == 0) { return std::round((out_min + out_max) / 2); } | |
const double value = ((double)(x - in_min) * out_length) / in_length + out_min; | |
return std::round(value); | |
} | |
long check(long x, long in_min, long in_max, long out_min, long out_max, long (*fmap) (long x, long in_min, long in_max, long out_min, long out_max)) { | |
const long floatvalue = test_map(x, in_min, in_max, out_min, out_max); | |
const long map_value = fmap(x, in_min, in_max, out_min, out_max); | |
return floatvalue - map_value; | |
} | |
int main() | |
{ | |
struct | |
{ | |
const char* name; | |
long (*fmap) (long x, long in_min, long in_max, long out_min, long out_max); | |
uint64_t err; | |
} tests [] = | |
{ | |
{ "arduino", map_arduino, 0 }, | |
{ "esp8266", map_esp8266, 0 }, | |
{ "test ", map_test, 0 }, | |
}; | |
constexpr long r = 500; | |
for (auto t: tests) | |
{ | |
timeval t1, t2; | |
gettimeofday(&t1, nullptr); | |
for (long in_max = 2; in_max < r; in_max++) | |
for (long out_max = 2; out_max < r; out_max++) | |
for (long x = 0; x < r; x++) | |
t.err += std::abs(check(x, 1, in_max, 1, out_max, t.fmap)); | |
gettimeofday(&t2, nullptr); | |
std::cout << t.name << ": sum = " << t.err << " (" << (t2.tv_sec - t1.tv_sec) + (t2.tv_usec - t1.tv_usec) / 1000000.0 << " seconds)" << std::endl; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@d-a-v
Changed the
main()
to extend testing like this:However I find it very suspicious that the
map_esp8266
function performs so well for large values.It simply can't perform that well for large values.
So I suspect this test can't be run on a x86 platform as the internals of the registers in the FPU use 80 bit registers.
So essentially this will perform not according to 'specs' based on the 32 bit long values.
Another point to take into account is that the inner loop should actually be run from -r ... r
With my slightly improved new version of the map function, the error for negative values of
x
is orders of magnitide smaller.So I have to look into where the rounding error is caused in my function for positive values of
x
.Slightly improved version: