Range node out of bounds?

Hi There,

Just a quick heads up - the range node can be brought to generating values outside of the configured bounds:

[
    {
        "id": "9b95f8806d032b1a",
        "type": "inject",
        "z": "ae38b2dbd23d1681",
        "g": "b4012d193105c803",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "13.1",
        "payloadType": "num",
        "x": 627,
        "y": 1175,
        "wires": [
            [
                "9867b9610c1786d5"
            ]
        ]
    },
    {
        "id": "9867b9610c1786d5",
        "type": "range",
        "z": "ae38b2dbd23d1681",
        "g": "b4012d193105c803",
        "minin": "0",
        "maxin": "9.5",
        "minout": "0",
        "maxout": "100.5",
        "action": "clamp",
        "round": true,
        "property": "payload",
        "name": "",
        "x": 954,
        "y": 1175,
        "wires": [
            [
                "4fbf1055ffadffe5"
            ]
        ]
    }
]

Range node is configured to target range of 0 - 100.5 and the value 13.1 is outside of the incoming range of 0 - 9.5, range node is configured to "limit to the target range".

This example will give a value of 101 because I also set the "round result to the nearest integer".

But the value should really be 100 since that's within the target range.

1 Like

Best place to report bugs is on github directly

1 Like

Btw doing the same test but with negative values works, i.e. target range of 0.5 to 100.5 and using a negative values gives 1 not 0, e.g.:

[{"id":"9d47e88afa90b312","type":"inject","z":"ae38b2dbd23d1681","g":"b4012d193105c803","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-13.1","payloadType":"num","x":784,"y":1231,"wires":[["a9b9805029fecc58"]]},{"id":"a9b9805029fecc58","type":"range","z":"ae38b2dbd23d1681","g":"b4012d193105c803","minin":"0","maxin":"9.5","minout":"0.5","maxout":"100.5","action":"clamp","round":true,"property":"payload","name":"","x":1121,"y":1231,"wires":[["eb733ddd81f3ed5a"]]}]

but will give zero if the start value is zero.

I did some more testing and found another strange configuration: what happens if the from value is less than the to value? I would expect an invalid node error or something. That's not the case and instead I can do something like this:

[{"id":"1ae5ab0fcaa82ddc","type":"group","z":"ccbf1eb483af3580","name":"limit to target range","style":{"label":true},"nodes":["16f237028e823fb8","6cbcee09b67d8279","bebdc09f44bef0d8","170f49712b8a82f6","a038e05f5cd72970","c16cdfde2b1b9eee","c6cbc0c6a054e9c6","184f031e24c22762","ae4b874bbd4c74d5","e9b4f4a4eac991fb","5b0d291ac537e80e","8581e538618eb3c8"],"x":57,"y":66,"w":697,"h":268},{"id":"16f237028e823fb8","type":"range","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","minin":"100","maxin":"0","minout":"0","maxout":"-100","action":"clamp","round":false,"property":"payload","name":"","x":380,"y":107,"wires":[["bebdc09f44bef0d8"]]},{"id":"6cbcee09b67d8279","type":"inject","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"40","payloadType":"num","x":161,"y":107,"wires":[["16f237028e823fb8"]]},{"id":"bebdc09f44bef0d8","type":"ut-assert-values","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","ignore_failure_if_succeed":false,"rules":[{"t":"eql","p":"payload","pt":"msg","to":"-100","tot":"num"}],"x":608,"y":107,"wires":[[]]},{"id":"170f49712b8a82f6","type":"range","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","minin":"100","maxin":"0","minout":"0","maxout":"-100","action":"clamp","round":false,"property":"payload","name":"","x":378,"y":173,"wires":[["c16cdfde2b1b9eee"]]},{"id":"a038e05f5cd72970","type":"inject","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-40","payloadType":"num","x":159,"y":173,"wires":[["170f49712b8a82f6"]]},{"id":"c16cdfde2b1b9eee","type":"ut-assert-values","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","ignore_failure_if_succeed":false,"rules":[{"t":"eql","p":"payload","pt":"msg","to":"-100","tot":"num"}],"x":606,"y":173,"wires":[[]]},{"id":"c6cbc0c6a054e9c6","type":"range","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","minin":"100","maxin":"0","minout":"0","maxout":"-100","action":"clamp","round":false,"property":"payload","name":"","x":372,"y":237,"wires":[["ae4b874bbd4c74d5"]]},{"id":"184f031e24c22762","type":"inject","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-20","payloadType":"num","x":153,"y":237,"wires":[["c6cbc0c6a054e9c6"]]},{"id":"ae4b874bbd4c74d5","type":"ut-assert-values","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","ignore_failure_if_succeed":false,"rules":[{"t":"eql","p":"payload","pt":"msg","to":"-100","tot":"num"}],"x":600,"y":237,"wires":[[]]},{"id":"e9b4f4a4eac991fb","type":"range","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","minin":"100","maxin":"0","minout":"0","maxout":"-100","action":"clamp","round":false,"property":"payload","name":"","x":374,"y":293,"wires":[["8581e538618eb3c8"]]},{"id":"5b0d291ac537e80e","type":"inject","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20","payloadType":"num","x":155,"y":293,"wires":[["e9b4f4a4eac991fb"]]},{"id":"8581e538618eb3c8","type":"ut-assert-values","z":"ccbf1eb483af3580","g":"1ae5ab0fcaa82ddc","name":"","ignore_failure_if_succeed":false,"rules":[{"t":"eql","p":"payload","pt":"msg","to":"-100","tot":"num"}],"x":602,"y":293,"wires":[[]]}]

The configuration of the range node is this:

Regardless of payload (at least those that I tried) the range node always gives me -100 - is that correct? For example, 20 should map to -20. Or 40 to -40. -20 and -40 are correct in being mapped to -100 being outside the input range.

yes - not quite as I would expect - yet if you specify them the other way round it works ie

from 0 - 100 –> -100 - 0 IE the inputs are expecting a minimum follow by a maximum to map.

Sure but that the configuration I created remains valid - herein lies the issue: I've created a valid configuration that is incorrect in its execution.

In writing my comment, I thought that perhaps having a from larger than a to is literally ok, i.e., no where in the documentation does it say that one has to be less than the other.

I don't know what the intention was when designing the range node, so I can't tell what is correct and what isn't.

EDIT: btw, I did think that the node would do an internally sorting of the values (from & to) to make sure an "invalid" configuration becomes valid but that doesn't seem to be the case.

Indeed - I don’t disagree - the fact that it works one way round but not the other is not ideal (nor correct). I’m happy to call it a bug and fix it - I can’t believe anyone would rely on the other situation to be the case for correct operation of a flow.

I doubt that too and spent sometime thinking about what it means to go from 100 to 0 ... in the end it's exactly the same as 0 to 100 if the target range is also reversed.

Btw I had a quick look at the implementation and was surprised about this line:

n = ((n - node.minin) % divisor + divisor) % divisor + node.minin;

Not only does JS support floats as modulo operators but also modulo'ing the divisor with itself :wink: I still don't understand the line - if I add the divisor to the result of doing a modulo, and then do another modulo, don't I just get the original modulo? ... aaaaah ok, you're inverting negative values - now I get it. Thanks for being that rubber duck :wink:

I'd go with fixing the validation in the frontend - from must be less than to and perhaps only integers? Not sure whether that's what is meant by number? I have the feeling that the original implementation assumes integer values?[1] Or is that just my imagination?

[1] = I was thinking that because of the modulo operation but if JS supports float modulo then I guess that is a non-issue.

It is perfectly valid to want to scale using float values (think thermocouples that typically output 0.000 ~ 0.041 volts for 0°C ~ 750°C)

1 Like

Cheers for the clarification.

Then I have to implement a modulo in Erlang that supports floating point values ... damn.

It is entirely reasonable for the node to support mapping from a 0-100 range to a 100-0 range (for example). That would be my expectation. Before changing anything, we need to properly understand the existing behaviour and decide if its sufficiently broken that no one is going to be relying on it if we change things.

I mean you could just as easily codify the existing "broken" behaviour and simply accept it as the behaviour of the range node. Here the broken behaviour I'm referring to is the 100 to 0 and 0 to -100 range (as shown above), not the out-of-bounds behaviour of the original pull request because that is simply wrong - zero and 101 aren't in the bounds of 0.3 - 100.5 (for example).

I'm only asking since I want to create a unit test for the range node and have that unit test work with Node-RED and Erlang-Red to have one and the same behaviour on both.

That could also be useful for the Node-RED project since then there is a single gold-standard for the behaviour of the existing nodes and breaking any backward compatibility would immediately error out on a unit test.

Indeed - we do have unit tests for the node already; clearly they don’t cover all combinations as discovered :slight_smile:

visual - in form of flows? :wink:

Just saying that if there was such a repertoire of visual unit tests, then any Node-RED user could create new unit tests to cover areas of usage which is important to them.

Plus such a test suite is great for communicating functionality of nodes ...