What does "constrainedValue" do?

In my code, I set a slider value to 55 using a variable that is a double.
Later in the code I use getValue() and notice that the value returned is 54.999…

I looked at JUCE’s setValue implementation and noticed that *constrainedValue" changed the value being stored.

I’m guessing this has something to do with the range and step I’m using.

Are there some general guidelines to avoid this kind of surprise?

    void setValue (double newValue, NotificationType notification)
{
    // for a two-value style slider, you should use the setMinValue() and setMaxValue()
    // methods to set the two values.
    jassert (style != TwoValueHorizontal && style != TwoValueVertical);

    newValue = constrainedValue (newValue);

    if (style == ThreeValueHorizontal || style == ThreeValueVertical)
    {
        jassert (static_cast<double> (valueMin.getValue()) <= static_cast<double> (valueMax.getValue()));

        newValue = jlimit (static_cast<double> (valueMin.getValue()),
                           static_cast<double> (valueMax.getValue()),
                           newValue);
    }

    if (newValue != lastCurrentValue)
    {
        if (valueBox != nullptr)
            valueBox->hideEditor (true);

        lastCurrentValue = newValue;

        // (need to do this comparison because the Value will use equalsWithSameType to compare
        // the new and old values, so will generate unwanted change events if the type changes)
        if (currentValue != newValue)
            currentValue = newValue;

        updateText();
        owner.repaint();
        updatePopupDisplay (newValue);

        triggerChangeMessage (notification);
    }
}

In a debugger, you should be able to stick a breakpoint in Slider::constrainedValue and see exactly what it does. By default, it will call snapToLegalValue on its internal NormalisableRange, so perhaps there is some unexpected rounding in snapToLegalValue.

If you need very specific snapping behaviour, you could consider calling setNormalisableRange on your slider, and passing a NormalisableRange with a non-default snapToLegalValueFunc.

Thanks for that @reuk. I didn’t have a chance to update my question.
I did just step into the “constrainedValue” call and found that floor was being called.

    /** Takes a non-normalised value and snaps it based on either the interval property of
    this NormalisableRange or the lambda function supplied to the constructor.
*/
ValueType snapToLegalValue (ValueType v) const noexcept
{
    if (snapToLegalValueFunction != nullptr)
        return snapToLegalValueFunction (start, end, v);

    if (interval > ValueType())
        v = start + interval * std::floor ((v - start) / interval + static_cast<ValueType> (0.5));

    return (v <= start || end <= start) ? start : (v >= end ? end : v);
}

The problem is that I am using an Incremental Slider. I’ve noticed that when I use NormalisableRange the increment and decrement buttons no longer work because a value of 0 is sent as the delta to incrementOrDecrement

    void incrementOrDecrement (double delta)
{
    if (style == IncDecButtons)
    {
        auto newValue = owner.snapValue (getValue() + delta, notDragging);

        if (currentDrag != nullptr)
        {
            setValue (newValue, sendNotificationSync);
        }
        else
        {
            DragInProgress drag (*this);
            setValue (newValue, sendNotificationSync);
        }
    }
}

@reuk ,

Here is how I’m setting up my normalisablerange

        NormalisableRange<double> normalisedRange(
        20.0,
        20000.0,
        null,
		null,             
        [this](auto rangeStart, auto rangeEnd, auto valueToRemap)
    {
        // My conditions
		
		
		return <some value>;
        
    });


    setNormalisableRange(normalisedRange);

@reuk

I added a value for the interval property and now everything appears to be working. My apologies for all this posting. It’s my way of thinking out loud I suppose.

Your suggestion on using snapToLegalValue, got me to search a bit harder. I realized, I wasn’t clear on the usage of NormalisableRange.

        NormalisableRange<double> normalisedRange(
    20.0,
    20000.0,
    null,
	null,             
    [this](auto rangeStart, auto rangeEnd, auto valueToRemap)
{
    // My conditions
	
	
	return <some value>;
    
});

normalisedRange.interval = 0.001;
setNormalisableRange(normalisedRange);