Skip to content

Instantly share code, notes, and snippets.

@d-a-v
Created July 19, 2023 21:24
Show Gist options
  • Save d-a-v/4e1cfe2448aaa4a9ded8f05599cf3f20 to your computer and use it in GitHub Desktop.
Save d-a-v/4e1cfe2448aaa4a9ded8f05599cf3f20 to your computer and use it in GitHub Desktop.
arduino map() tests
// 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;
}
}
@TD-er
Copy link

TD-er commented Jul 20, 2023

@d-a-v

Changed the main() to extend testing like this:

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 gridsize = 500;
    constexpr long r = 1000000000;
    constexpr long s = 2;
    constexpr long step = (r - s + (gridsize/2))/gridsize;

    std::cout << "gridsize = " << gridsize << " r = " << r << " s = " << s << " step = " << step << std::endl;

    for (auto t: tests)
    {
        timeval t1, t2;
        gettimeofday(&t1, nullptr);
        for (long in_max = s; in_max < r; in_max+=step)
            for (long out_max = s; out_max < r; out_max+=step)
                for (long x = 0; x < r; x += step)
                    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;
    }
}

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:

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)) {
        const long shift_factor = (x < in_min) ? (-1 - ((in_min - x) / in_length)) : (1 + ((x - in_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 >> 1)) / 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 >> 1)) / factor + out_min;
    const long error         = (delta * (out_max - estimate_full)) / in_length;
    return delta / factor + out_min + error;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment