Simple JUCE bug fix (Number formating)

Came across this bug which should be easy to reproduce and to fix, so here goes:


        size_t writeDouble (double n, int numDecPlaces, bool useScientificNotation)
        {
            {
                std::ostream o (this);

                if (numDecPlaces > 0)
                {
                    o.setf (useScientificNotation ? std::ios_base::scientific : std::ios_base::fixed);
                    o.precision ((std::streamsize) numDecPlaces);
                }

                o << n;
            }

            return (size_t) (pptr() - pbase());
        }

The function above gets called when you call e.g. “juce::String(value, 0, false)”

For values larger than 6 digits (for example: 29888016.0), you get back scientific notation (for example: “2.9106e+07”), even though scientific notation is disabled by the 3rd parameter to String, which is correctly passed along to writeDouble but ignored in this case.

Platform: Win64 (Windows 11) [untested on other platforms]

I’ve recreated your issue in a unit test I’ll take a look today and see what I come back with. If you need a work around for now I think adding 1 decimal place will get you what you want. You could always remove or replace the last character(s) in the string.

1 Like

I should have looked closer at the docs…

@param numberOfDecimalPlaces if this is > 0, it will format the number using that many
decimal places, adding trailing zeros as required, and
will not use exponent notation. If 0 or less, it will use
exponent notation if necessary.

This is the intended behaviour maybe we could add a jassert if numberOfDecimalPlaces is 0 and useScientificNotation is false (and only if these are passed explicitly). At least you could have hit that and been warned in this case?

No offence here (!) but this to me sounds like there was a bug/unexpected behaviour and when it came up, the docs were changed :joy:
Can’t you just move the precision call outside of the if?

@Rincewind possibly. I’m looking at this a little closer and I think I can make it much more intuitive with the breaking changes only occurring for code that probably has unexpected behaviour anyway. Can’t promise it will make it in but let’s see.

The easiest way around this right now is probably something like juce::String (value, 1, false).dropLastCharacters (2)

1 Like

No not quite it’s used by all the constructors. I’m considering something like this internally…

        size_t writeDouble (double n, std::optional<int> numDecPlaces, std::optional<bool> useScientificNotation)
        {
            {
                std::ostream o (this);

                if (useScientificNotation)
                    o.setf (useScientificNotation.value() ? std::ios_base::scientific : std::ios_base::fixed);

                if (numDecPlaces)
                    o.precision ((std::streamsize) numDecPlaces.value());

                o << n;
            }

            return (size_t) (pptr() - pbase());
        }
1 Like

Yep, I was thinking that it might have to boil down to this.

1 Like

I’m not back until Wednesday but I did get something into an internal MR. What I can say is we’ve decided not to change the existing constructors because the potential ramifications for those that passed a value of zero expecting different behaviour is unfortunately too great in this instance. What we have as a working prototype right now would mean using it like so…

juce::String { 29888016.0, StringFloatFormatOptions{}.withNumberOfDecimals (0) };

but please note this could change pending a review.