Negative values from MultiMap function

Hi all,

I was trying to use this MultiMap function in my project.
https://www.arduino.cc/reference/en/libraries/multimap/

However, I found some negative output from my test.
Here is the code I tested. Simply print out the input value from 0 to 200.

#include "MultiMap.h"

int mappedValue;

int in[] = {0 , 200, 500, 1023};
int out[] = {0, 500, 1000, 1023};

void setup() {
  Serial.begin(9600);

  for (int i = 0; i <=200; i++){
    Serial.print("Raw value: ");
    Serial.println(i);

    mappedValue = multiMap<int>(i, in, out, 4);
    Serial.print("Mapped value: ");
    Serial.println(mappedValue);
  }
  
}

void loop() {

}

Here are the out put values.

Raw value: 0
Mapped value: 0
Raw value: 1
Mapped value: 2
Raw value: 2
Mapped value: 5
Raw value: 3
Mapped value: 7
Raw value: 4
Mapped value: 10
Raw value: 5
Mapped value: 12
Raw value: 6
Mapped value: 15
Raw value: 7
Mapped value: 17
Raw value: 8
Mapped value: 20
Raw value: 9
Mapped value: 22
Raw value: 10
Mapped value: 25
Raw value: 11
Mapped value: 27
Raw value: 12
Mapped value: 30
Raw value: 13
Mapped value: 32
Raw value: 14
Mapped value: 35
Raw value: 15
Mapped value: 37
Raw value: 16
Mapped value: 40
Raw value: 17
Mapped value: 42
Raw value: 18
Mapped value: 45
Raw value: 19
Mapped value: 47
Raw value: 20
Mapped value: 50
Raw value: 21
Mapped value: 52
Raw value: 22
Mapped value: 55
Raw value: 23
Mapped value: 57
Raw value: 24
Mapped value: 60
Raw value: 25
Mapped value: 62
Raw value: 26
Mapped value: 65
Raw value: 27
Mapped value: 67
Raw value: 28
Mapped value: 70
Raw value: 29
Mapped value: 72
Raw value: 30
Mapped value: 75
Raw value: 31
Mapped value: 77
Raw value: 32
Mapped value: 80
Raw value: 33
Mapped value: 82
Raw value: 34
Mapped value: 85
Raw value: 35
Mapped value: 87
Raw value: 36
Mapped value: 90
Raw value: 37
Mapped value: 92
Raw value: 38
Mapped value: 95
Raw value: 39
Mapped value: 97
Raw value: 40
Mapped value: 100
Raw value: 41
Mapped value: 102
Raw value: 42
Mapped value: 105
Raw value: 43
Mapped value: 107
Raw value: 44
Mapped value: 110
Raw value: 45
Mapped value: 112
Raw value: 46
Mapped value: 115
Raw value: 47
Mapped value: 117
Raw value: 48
Mapped value: 120
Raw value: 49
Mapped value: 122
Raw value: 50
Mapped value: 125
Raw value: 51
Mapped value: 127
Raw value: 52
Mapped value: 130
Raw value: 53
Mapped value: 132
Raw value: 54
Mapped value: 135
Raw value: 55
Mapped value: 137
Raw value: 56
Mapped value: 140
Raw value: 57
Mapped value: 142
Raw value: 58
Mapped value: 145
Raw value: 59
Mapped value: 147
Raw value: 60
Mapped value: 150
Raw value: 61
Mapped value: 152
Raw value: 62
Mapped value: 155
Raw value: 63
Mapped value: 157
Raw value: 64
Mapped value: 160
Raw value: 65
Mapped value: 162
Raw value: 66
Mapped value: -162
Raw value: 67
Mapped value: -160
Raw value: 68
Mapped value: -157
Raw value: 69
Mapped value: -155
Raw value: 70
Mapped value: -152
Raw value: 71
Mapped value: -150
Raw value: 72
Mapped value: -147
Raw value: 73
Mapped value: -145
Raw value: 74
Mapped value: -142
Raw value: 75
Mapped value: -140
Raw value: 76
Mapped value: -137
Raw value: 77
Mapped value: -135
Raw value: 78
Mapped value: -132
Raw value: 79
Mapped value: -130
Raw value: 80
Mapped value: -127
Raw value: 81
Mapped value: -125
Raw value: 82
Mapped value: -122
Raw value: 83
Mapped value: -120
Raw value: 84
Mapped value: -117
Raw value: 85
Mapped value: -115
Raw value: 86
Mapped value: -112
Raw value: 87
Mapped value: -110
Raw value: 88
Mapped value: -107
Raw value: 89
Mapped value: -105
Raw value: 90
Mapped value: -102
Raw value: 91
Mapped value: -100
Raw value: 92
Mapped value: -97
Raw value: 93
Mapped value: -95
Raw value: 94
Mapped value: -92
Raw value: 95
Mapped value: -90
Raw value: 96
Mapped value: -87
Raw value: 97
Mapped value: -85
Raw value: 98
Mapped value: -82
Raw value: 99
Mapped value: -80
Raw value: 100
Mapped value: -77
Raw value: 101
Mapped value: -75
Raw value: 102
Mapped value: -72
Raw value: 103
Mapped value: -70
Raw value: 104
Mapped value: -67
Raw value: 105
Mapped value: -65
Raw value: 106
Mapped value: -62
Raw value: 107
Mapped value: -60
Raw value: 108
Mapped value: -57
Raw value: 109
Mapped value: -55
Raw value: 110
Mapped value: -52
Raw value: 111
Mapped value: -50
Raw value: 112
Mapped value: -47
Raw value: 113
Mapped value: -45
Raw value: 114
Mapped value: -42
Raw value: 115
Mapped value: -40
Raw value: 116
Mapped value: -37
Raw value: 117
Mapped value: -35
Raw value: 118
Mapped value: -32
Raw value: 119
Mapped value: -30
Raw value: 120
Mapped value: -27
Raw value: 121
Mapped value: -25
Raw value: 122
Mapped value: -22
Raw value: 123
Mapped value: -20
Raw value: 124
Mapped value: -17
Raw value: 125
Mapped value: -15
Raw value: 126
Mapped value: -12
Raw value: 127
Mapped value: -10
Raw value: 128
Mapped value: -7
Raw value: 129
Mapped value: -5
Raw value: 130
Mapped value: -2
Raw value: 131
Mapped value: 0
Raw value: 132
Mapped value: 2
Raw value: 133
Mapped value: 4
Raw value: 134
Mapped value: 7
Raw value: 135
Mapped value: 9
Raw value: 136
Mapped value: 12
Raw value: 137
Mapped value: 14
Raw value: 138
Mapped value: 17
Raw value: 139
Mapped value: 19
Raw value: 140
Mapped value: 22
Raw value: 141
Mapped value: 24
Raw value: 142
Mapped value: 27
Raw value: 143
Mapped value: 29
Raw value: 144
Mapped value: 32
Raw value: 145
Mapped value: 34
Raw value: 146
Mapped value: 37
Raw value: 147
Mapped value: 39
Raw value: 148
Mapped value: 42
Raw value: 149
Mapped value: 44
Raw value: 150
Mapped value: 47
Raw value: 151
Mapped value: 49
Raw value: 152
Mapped value: 52
Raw value: 153
Mapped value: 54
Raw value: 154
Mapped value: 57
Raw value: 155
Mapped value: 59
Raw value: 156
Mapped value: 62
Raw value: 157
Mapped value: 64
Raw value: 158
Mapped value: 67
Raw value: 159
Mapped value: 69
Raw value: 160
Mapped value: 72
Raw value: 161
Mapped value: 74
Raw value: 162
Mapped value: 77
Raw value: 163
Mapped value: 79
Raw value: 164
Mapped value: 82
Raw value: 165
Mapped value: 84
Raw value: 166
Mapped value: 87
Raw value: 167
Mapped value: 89
Raw value: 168
Mapped value: 92
Raw value: 169
Mapped value: 94
Raw value: 170
Mapped value: 97
Raw value: 171
Mapped value: 99
Raw value: 172
Mapped value: 102
Raw value: 173
Mapped value: 104
Raw value: 174
Mapped value: 107
Raw value: 175
Mapped value: 109
Raw value: 176
Mapped value: 112
Raw value: 177
Mapped value: 114
Raw value: 178
Mapped value: 117
Raw value: 179
Mapped value: 119
Raw value: 180
Mapped value: 122
Raw value: 181
Mapped value: 124
Raw value: 182
Mapped value: 127
Raw value: 183
Mapped value: 129
Raw value: 184
Mapped value: 132
Raw value: 185
Mapped value: 134
Raw value: 186
Mapped value: 137
Raw value: 187
Mapped value: 139
Raw value: 188
Mapped value: 142
Raw value: 189
Mapped value: 144
Raw value: 190
Mapped value: 147
Raw value: 191
Mapped value: 149
Raw value: 192
Mapped value: 152
Raw value: 193
Mapped value: 154
Raw value: 194
Mapped value: 157
Raw value: 195
Mapped value: 159
Raw value: 196
Mapped value: 162
Raw value: 197
Mapped value: -162
Raw value: 198
Mapped value: -160
Raw value: 199
Mapped value: -157
Raw value: 200
Mapped value: 500

Does anyone know why there are negative output values in this code/function?
Thanks

I don't have an explanation (yet) but I do get the same results on my Arduino UNO.

EDIT:
The answer is an integer overflow. When mapping the range 0-200 to the range 0-500 the MultiMap returns:
(val - 0) * (500 - 0) / (200 - 0) + 0;

When 'val' is 66, (val - 0) * (500 - 0) results in 33000, but 16-bit int can only hold up to 32767. Values above that wrap to negative numbers. Dividing the negative number by 200 results in a value that is still negative.

One way to work around the problem is to use smaller intervals. If you change the tables to:

int in[] = {0 , 100, 200, 500, 1023};
int out[] = {0, 250, 500, 1000, 1023};

The first interval will max out at:
(val - 0) * (250 - 0) / (100 - 0) + 0;
That gives a worst-case of 100 * 250 = 25000 which will fit in an int.

The second interval will be:
(val - 100) * (500 - 250) / (250 - 100) + 250;
Again, the worst case will be 100 * 250 = 25000.

Just make sure that the width of each input interval times the width of the corresponding output interval is less than the maximum value for the data type.

1 Like

From the documentation:

Multimap() automatically constrains the output to the first and last value in the output[] array.

but this is not what is happening.

EDIT: I can reproduce that issue, too. One possible workaround is to declare everything as unsigned int. For sure, this is a peculiar library, though. For one, there is no .cpp file: the implementation itself is done within the header. It is technically legal, but still bad practice to do so.

It only clips to the first and last values. It assumes that mapping in-between will work and thus not produce values outside the output range.

I wonder how one would write the template so that integer types were cast to a wider integer before multiplying. Of course, it would still have to work for float, double, complex, BigNum, and any other types for which basic math operations are defined.

That won't fix it since 200 * 500 is 100,000 and the unsigned int can only hold up to 65,535 and will still overflow.

When I tested my solution, it seemed to work, that is, it eliminated negative values and the output seemed superficially plausible to me: I didn't investigate further. If things are as you noticed, then the implementation is quite poor: a function that takes int values ought to work correctly across the full range of possible int values, not behave like this. It would have been better to restrict the function to floats and avoid the overflow altogether, or to use a long int internally and cast the result to an int only at the and of the computations.

Thanks for your help.
I'll add more interval to prevent this issue.