From 5c89d6baec330828a6275707977d2e4f0c9d6e77 Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Mon, 25 May 2026 15:54:41 +0200 Subject: [PATCH] fix #15, support decreasing unsigned int output array --- CHANGELOG.md | 9 +- MultiMap.h | 73 ++++++++++-- README.md | 20 ++-- examples/multimap_2d/multimap_2d.ino | 2 +- examples/multimap_BS_compare/output_0.4.0.txt | 105 ++++++++++++++++++ .../multimap_decr_unsigned_test.ino | 47 ++++++++ .../multimap_demo_fail/multimap_demo_fail.ino | 2 + examples/multimap_timing/output_0.4.0.txt | 13 +++ library.json | 2 +- library.properties | 2 +- 10 files changed, 251 insertions(+), 24 deletions(-) create mode 100644 examples/multimap_BS_compare/output_0.4.0.txt create mode 100644 examples/multimap_decr_unsigned_test/multimap_decr_unsigned_test.ino create mode 100644 examples/multimap_timing/output_0.4.0.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index dc10fc3..e00d4a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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** as it was missing. - update readme.md - update GitHub actions diff --git a/MultiMap.h b/MultiMap.h index 450cdbd..2368d39 100644 --- a/MultiMap.h +++ b/MultiMap.h @@ -2,7 +2,7 @@ // // 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 @@ -10,7 +10,7 @@ -#define MULTIMAP_LIB_VERSION (F("0.3.0")) +#define MULTIMAP_LIB_VERSION (F("0.4.0")) #include "Arduino.h" @@ -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]; } @@ -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; @@ -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]; } @@ -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]; } @@ -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]; } @@ -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]; } diff --git a/README.md b/README.md index 981c065..25ed578 100644 --- a/README.md +++ b/README.md @@ -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. @@ -57,13 +57,13 @@ 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). @@ -71,10 +71,10 @@ Drawback is it might take extra memory (e.g. int16_t => float). 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 diff --git a/examples/multimap_2d/multimap_2d.ino b/examples/multimap_2d/multimap_2d.ino index c03e05d..4009743 100644 --- a/examples/multimap_2d/multimap_2d.ino +++ b/examples/multimap_2d/multimap_2d.ino @@ -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(i, in, xc, size); float y = multiMap(i, in, yc, size); // Serial.print(i); diff --git a/examples/multimap_BS_compare/output_0.4.0.txt b/examples/multimap_BS_compare/output_0.4.0.txt new file mode 100644 index 0000000..bf81873 --- /dev/null +++ b/examples/multimap_BS_compare/output_0.4.0.txt @@ -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 + diff --git a/examples/multimap_decr_unsigned_test/multimap_decr_unsigned_test.ino b/examples/multimap_decr_unsigned_test/multimap_decr_unsigned_test.ino new file mode 100644 index 0000000..3d3dd51 --- /dev/null +++ b/examples/multimap_decr_unsigned_test/multimap_decr_unsigned_test.ino @@ -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(x, in, out, size); + Serial.print(x); + Serial.print("\t"); + Serial.println(y, 1); // 1 decimal + } +} + + +void loop() +{ +} + + +// -- END OF FILE -- diff --git a/examples/multimap_demo_fail/multimap_demo_fail.ino b/examples/multimap_demo_fail/multimap_demo_fail.ino index ea9828d..6e9246e 100644 --- a/examples/multimap_demo_fail/multimap_demo_fail.ino +++ b/examples/multimap_demo_fail/multimap_demo_fail.ino @@ -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" diff --git a/examples/multimap_timing/output_0.4.0.txt b/examples/multimap_timing/output_0.4.0.txt new file mode 100644 index 0000000..275f4e6 --- /dev/null +++ b/examples/multimap_timing/output_0.4.0.txt @@ -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 : 20 +121.0000 +time : 92 +121.0909 + +done... diff --git a/library.json b/library.json index 252a402..8a4924c 100644 --- a/library.json +++ b/library.json @@ -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": "*", diff --git a/library.properties b/library.properties index e156e7c..280377f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=MultiMap -version=0.3.0 +version=0.4.0 author=Rob Tillaart maintainer=Rob Tillaart sentence=Library for fast non-linear interpolation by means of two arrays.