Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).


## [0.4.0] - 2026-05-25
- fix #15, decreasing unsigned out array's cause underflow
- add multimap_decr_unsigned_test.ino test for #15
- minor edits

----

## [0.3.0] - 2026-02-23
- fix #13, allow size to be 65535 (uint16_t)
- add patch for possible interpolation under/overflows.
- add patch for possible interpolation under/overflows.
- add multi type **multiMapCache<T1, T2>** as it was missing.
- update readme.md
- update GitHub actions
Expand Down
73 changes: 63 additions & 10 deletions MultiMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
//
// FILE: MultiMap.h
// AUTHOR: Rob Tillaart
// VERSION: 0.3.0
// VERSION: 0.4.0
// DATE: 2011-01-26
// PURPOSE: Arduino library for fast non-linear mapping or interpolation of values
// URL: https://github.com/RobTillaart/MultiMap
// URL: http://playground.arduino.cc/Main/MultiMap



#define MULTIMAP_LIB_VERSION (F("0.3.0"))
#define MULTIMAP_LIB_VERSION (F("0.4.0"))


#include "Arduino.h"
Expand All @@ -36,7 +36,16 @@ T multiMap(T value, T* _in, T* _out, uint16_t size)
if (value == _in[pos]) return _out[pos];

// interpolate in the right segment for the rest
return (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
// use a modified formula for decreasing output array segment
// to prevent unsigned "underflow/overflow" (issue #15)
if (_out[pos] >= _out[pos-1])
{
return _out[pos-1] + (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]);
}
else
{
return _out[pos-1] - (value - _in[pos-1]) * (_out[pos-1] - _out[pos]) / (_in[pos] - _in[pos-1]);
}
// if interpolation overflows use this line
// return T(float(value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1])) + _out[pos-1];
}
Expand Down Expand Up @@ -85,9 +94,16 @@ T multiMapCache(T value, T* _in, T* _out, uint16_t size)
else
{
// interpolate in the right segment for the rest
cache = (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
// if interpolation overflows use this line
// cache = T(float(value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1])) + _out[pos-1];
// use a modified formula for decreasing output array segment
// to prevent unsigned "underflow/overflow" (issue #15)
if (_out[pos] >= _out[pos-1])
{
cache = _out[pos-1] + (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]);
}
else
{
cache = _out[pos-1] - (value - _in[pos-1]) * (_out[pos-1] - _out[pos]) / (_in[pos] - _in[pos-1]);
}
}
}
return cache;
Expand Down Expand Up @@ -119,7 +135,16 @@ T multiMapBS(T value, T* _in, T* _out, uint16_t size)
else upper = mid;
}
// interpolate in the right segment for the rest
return (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]) + _out[lower];
// use a modified formula for decreasing output array segment
// to prevent unsigned "underflow/overflow" (issue #15)
if (_out[upper] >= _out[lower])
{
return _out[lower] + (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]);
}
else
{
return _out[lower] - (value - _in[lower]) * (_out[lower] - _out[upper]) / (_in[upper] - _in[lower]);
}
// if interpolation overflows use this line
// return T(float(value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower])) + _out[lower];
}
Expand All @@ -145,7 +170,16 @@ T2 multiMap(T1 value, T1* _in, T2* _out, uint16_t size)
if (value == _in[pos]) return _out[pos];

// interpolate in the right segment for the rest
return (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
// use a modified formula for decreasing output array segment
// to prevent unsigned "underflow/overflow" (issue #15)
if (_out[pos] >= _out[pos-1])
{
return _out[pos-1] + (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]);
}
else
{
return _out[pos-1] - (value - _in[pos-1]) * (_out[pos-1] - _out[pos]) / (_in[pos] - _in[pos-1]);
}
// if interpolation overflows use this line
// return T2(float(value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1])) + _out[pos-1];
}
Expand Down Expand Up @@ -194,7 +228,17 @@ T2 multiMapCache(T1 value, T1* _in, T2* _out, uint16_t size)
else
{
// interpolate in the right segment for the rest
cache = (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
// use a modified formula for decreasing output array segment
// to prevent unsigned "underflow/overflow" (issue #15)
if (_out[pos] >= _out[pos-1])
{
cache = _out[pos-1] + (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]);

}
else
{
cache = _out[pos-1] - (value - _in[pos-1]) * (_out[pos-1] - _out[pos]) / (_in[pos] - _in[pos-1]);
}
// if interpolation overflows use this line
// return T2(float(value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1])) + _out[pos-1];
}
Expand Down Expand Up @@ -227,7 +271,16 @@ T2 multiMapBS(T1 value, T1* _in, T2* _out, uint16_t size)
else upper = mid;
}
// interpolate in the right segment for the rest
return (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]) + _out[lower];
// use a modified formula for decreasing output array segment
// to prevent unsigned "underflow/overflow" (issue #15)
if (_out[upper] >= _out[lower])
{
return _out[lower] + (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]);
}
else
{
return _out[lower] - (value - _in[lower]) * (_out[lower] - _out[upper]) / (_in[upper] - _in[lower]);
}
// if interpolation overflows use this line
// return T2(float(value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower])) + _out[lower];
}
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ multiple linear line segments.
Of course this approximation introduces an error.
By increasing the number of points and choose their position strategically the average error
can and will be reduced.
An important feature of the **multiMap()** is that the points do not need to have the same
distance (non-equidistant). This allows to have more pointe where needed (curvy line) and
less point where possible (straight lines).
An important feature of the **multiMap()** is that the input points do not need to have the same
distance (non-equidistant). This allows to have more data pointe where needed (curvy segments) and
less point where possible (straight segments).

Note: some functions are hard to approximate even with **multiMap()** as they go to infinity
or have a singularity.
Expand All @@ -57,24 +57,24 @@ However there might be more than one input value mapping onto the same output va
See - https://en.wikipedia.org/wiki/Bijection,_injection_and_surjection


### Math problems in interpolation (rare).
### Math problems in interpolation. (rare)

In multiMap the interpolation math can fail. This happens when integer types are used
that are too small to handle the multiplication in the interpolation (e.g. 8 or 16 bit)
Also when a mixed signed / unsigned types are used this can happen.
that are **too small** to handle the multiplication in the interpolation (e.g. 8 or 16 bit)
Also when a **mixed signed / unsigned** types are used failure can happen.

There are two solutions to handle this.
There are solutions to handle this.
- (preferred) use float (double) as either input or output type.
This forces casting to float in the interpolation solving the math problem.
Drawback is it might take extra memory (e.g. int16_t => float).
- The multiMap.h file contains **commented** code to cast the interpolation
during the interpolation only.
Comment the existing interpolation and uncomment the other line.
This saves memory as the arrays used do not "grow".
- The use of **decreasing unsigned** variables for the output array causes overflow
due to subtraction in the formula used. (issue #15, solved in 0.4.0)

Note both solutions have a performance penalty

See the sketch **multimap_demo_fail.ino** (UNO R3) to show the problem.
Note some solutions have a performance penalty


### 0.3.0 Breaking change
Expand Down
2 changes: 1 addition & 1 deletion examples/multimap_2d/multimap_2d.ino
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void setup()

for (int i = 0; i <= 360; i++)
{
// 2D mapping is doen by mapping twice (not too efficient but it works sort of.
// 2D mapping is done by mapping twice (not too efficient but it works sort of.
float x = multiMap<float>(i, in, xc, size);
float y = multiMap<float>(i, in, yc, size);
// Serial.print(i);
Expand Down
105 changes: 105 additions & 0 deletions examples/multimap_BS_compare/output_0.4.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
BOARD: UNO R3
IDE: 1.8.19


Arduino\libraries\MultiMap\examples\multimap_BS_compare\multimap_BS_compare.ino
MULTIMAP_LIB_VERSION: 0.4.0

size t1 t2 ratio
4 5732 5648 98.53
5 6288 6260 99.55
6 6868 6904 100.52
7 7456 7540 101.13
8 8064 8188 101.54
9 8704 8828 101.42
10 9356 9488 101.41
11 10032 10152 101.20
12 10720 10812 100.86
13 11428 11476 100.42
14 12156 12148 99.93
15 12908 12804 99.19
16 13680 13472 98.48
17 14464 14140 97.76
18 15268 14828 97.12
19 16096 15508 96.35
20 16944 16188 95.54
21 17804 16880 94.81
22 18684 17568 94.03
23 19592 18256 93.18
24 20516 18940 92.32
25 21456 19632 91.50
26 22416 20320 90.65
27 23396 21008 89.79
28 24400 21688 88.89
29 25420 22384 88.06
30 26460 23072 87.20
31 27520 23756 86.32
32 28600 24444 85.47
33 29696 25136 84.64
34 30808 25840 83.87
35 31948 26548 83.10
36 33100 27260 82.36
37 34276 27968 81.60
38 35472 28676 80.84
39 36684 29380 80.09
40 37916 30096 79.38
41 39168 30804 78.65
42 40436 31512 77.93
43 41732 32216 77.20
44 43040 32932 76.51
45 44368 33644 75.83
46 45724 34352 75.13
47 47088 35060 74.46
48 48476 35768 73.78
49 49880 36488 73.15
50 51308 37200 72.50
51 52752 37904 71.85
52 54220 38608 71.21
53 55708 39320 70.58
54 57208 40036 69.98
55 58728 40748 69.38
56 60272 41452 68.77
57 61836 42164 68.19
58 63416 42880 67.62
59 65020 43584 67.03
60 66636 44296 66.47
61 68276 45012 65.93
62 69936 45724 65.38
63 71616 46432 64.83
64 73308 47144 64.31
65 75028 47856 63.78
66 76764 48584 63.29
67 78520 49316 62.81
68 80300 50048 62.33
69 82088 50780 61.86
70 83904 51508 61.39
71 85736 52236 60.93
72 87592 52972 60.48
73 89456 53708 60.04
74 91352 54432 59.58
75 93260 55168 59.16
76 95192 55892 58.72
77 97140 56628 58.30
78 99104 57364 57.88
79 101092 58096 57.47
80 103104 58832 57.06
81 105124 59568 56.66
82 107180 60284 56.25
83 109240 61028 55.87
84 111332 61752 55.47
85 113436 62488 55.09
86 115548 63224 54.72
87 117700 63956 54.34
88 119860 64688 53.97
89 122048 65424 53.61
90 124240 66156 53.25
91 126464 66888 52.89
92 128704 67616 52.54
93 130964 68356 52.19
94 133240 69092 51.86
95 133404 69180 51.86
96 133404 69208 51.88
97 133404 69260 51.92
98 133404 69260 51.92
99 133408 69280 51.93

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// FILE: multimap_decr_unsigned_test.ino
// AUTHOR: Rob Tillaart
// PURPOSE: minimal demo
// URL: https://github.com/RobTillaart/MultiMap
//
// test for issue #15, decreasing unsigned

#include "MultiMap.h"

// note the IN array is not equidistant.
// layout is to see the points of the graph.
float in[] = { 0, 20, 40, 50, 60, 80, 90, 100, 105 };
uint16_t out[] = { 1000, 900, 800, 600, 300, 150, 75, 40, 20 };

int size = 9;


void setup()
{
// while(!Serial);
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();

Serial.println("X\tY");

for (int i = 0; i <= 500; i++)
{
float x = i * 0.2;
float y = multiMap<float, uint16_t>(x, in, out, size);
Serial.print(x);
Serial.print("\t");
Serial.println(y, 1); // 1 decimal
}
}


void loop()
{
}


// -- END OF FILE --
2 changes: 2 additions & 0 deletions examples/multimap_demo_fail/multimap_demo_fail.ino
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// due to a mix of signed and unsigned math.
// If you need this mapping and it fails for you
// the interpolation in MultiMap.h needs the casting line.
//
// 0.4.0 seems to have solved this bug


#include "MultiMap.h"
Expand Down
13 changes: 13 additions & 0 deletions examples/multimap_timing/output_0.4.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

BOARD: UNO R3
IDE: 1.8.19

...Arduino\libraries\MultiMap\examples\multimap_timing\multimap_timing.ino
MULTIMAP_LIB_VERSION: 0.4.0

time <int>: 20
121.0000
time <float>: 92
121.0909

done...
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/MultiMap.git"
},
"version": "0.3.0",
"version": "0.4.0",
"license": "MIT",
"frameworks": "*",
"platforms": "*",
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=MultiMap
version=0.3.0
version=0.4.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Library for fast non-linear interpolation by means of two arrays.
Expand Down
Loading