[FR] jmax, jmin, jlimit, Why no jbetween/jwithin?

I feel like every project I work on ends up having some kind of bool between(val, min, max) written. Why doesn’t JUCE have generic functions for this? It has jmin/max/etc… Seems like a missing piece.

template<typename NumericType> 
bool jwithinInclusive(NumericType value, NumericType lowerBound, NumericType upperBound) 
    return lowerBound <= value && value <= upperBound; 

template<typename NumericType> 
bool jwithinExclusive(NumericType value, NumericType lowerBound, NumericType upperBound) 
    return lowerBound < value && value < upperBound; 

Perhaps there are better descriptive names that could be used instead…

1 Like

I thought about this a couple of times as well, it seems a no-brainer, but actually, there are four variants to consider. Each comparison could be inclusive or exclusive. It becomes messy, while writing the actual equation is immediately obvious.

The only benefit is, if the value to test is an expression, you don’t have to write that expression twice, and you don’t have to assign a temp variable.

The second benefit would be a variant, where I don’t have to take care, if the lower bound is actually lower than the upper bound:

bool isBetween (Type v, Type b1, Type b2)
    return b1 < b2 ? v < b2 && v > b1
                   : v < b1 && v > b2;

But the obvious cases I would prefer to spell out as normal.
Having said that, those are free functions, there is no reason, why they have to be in the API. You can always define them yourself…



requires construction of a Range object.

if( ! Range<float>(2,3).contains(val) )


That’s kinda verbose compared to if( jbetween(val, 2, 3) ) !

I think you missed my opening sentence:

It’s a tricky problem because of the inclusive/exclusive needs everyone will have.

Sure, but if I saw some code that said

if (between (a, b, c))

then I’d have no idea which of those arguments is the value, and which the range. Writing it as a Range makes it totally clear what’s going on.

jlimit is very old, and if I was writing it again now, I’d make sure it took a range structure to force it to be written as

juce::limit ({ a, b }, c)

which makes it clear what the args mean. The same would be good practice for a between function.

However, having said that, I’ve never felt the need for a between function - otherwise I’d have added one by now.

Maybe that’s because in code I write nowadays, I’d avoid storing anything range-like as a pair of loose variables, and instead would already have it stored and manipulated in a Range, so can just call contains() on it.

1 Like

that is a well-constructed argument!

I suppose jlimit was written when using the variable placeholders in xcode or visual studio was the way you’d know which arguments were for which parameter. At least, that’s how I know what argument goes with which param when calling it. I never remember after the fact unless I’m using it like this jlimit( value, 0.0, 1.0 ).

I’ll use Range::contains.

template< typename ValueT >
class value
    ValueT val;
    value( ValueT v ) : val( v ){}

    bool isBetweenExcl( ValueT begin, ValueT end )
        return val > begin && val < end;

    bool isBetweenIncl( ValueT begin, ValueT end )
        return val >= begin && val <= end;

void Test()
    auto val = getMagicVal();
    if( value( val ).isBetweenIncl( begin, end ) )

This could be another way around the argument order ambiguity. You could fill it out further with other functions…

value( val ).orMax( max );
value( val ).orMin( min );
value( val ).clippedTo( min, max );
value( val ).percentBetween( min, max );

Sort of similar to using Range functions, but I kinda prefer having the value up front. When reading it within the context of a larger expression, “…a value clipped by this range…” seems more natural than “…this range clipping a value…” since it’s the value itself that you’re passing into the function or evaluating within a sub-expression.