Underflow, overflow and denormals

Hi guys,
i’m struggling with an issue where one of my plugins randomly clicks. After weeks of analysis, testing and refactoring, I found out that sometimes my code creates a value that is too small. I’m already using JUCE_SNAP_TO_ZERO macro, but is not helping. I wrote a function that is helping, but I’m sure there’s a better way to handle that.

inline float cleanUnderflow(float input)

{

    float result = input;

    if(input <= -9.94626945E+017)
    {
        DBG("Underflow detected: " << input);
        result = 0.0f;
    }
    
    return result;
}

Any advice, please?

Cheers!

You probably wanted to write e-17 and not e+17… Anyway, there’s basically two approaches - flush the numbers, either manually - like you do - or automatically by setting the internal cpu flags to disable denormal values, or feed some low level noise into the input stage (can be an alternating dc offset each block or whatever).

Loud clicking doesn’t sound like denormal/underflow issues, unless you somehow dividing by some value derived by the input (envelope / gain adjustment)?

1 Like

There are macro constants for that purpose:
http://www.cplusplus.com/reference/cfloat/
in your case FLT_MIN I guess.

But that’s only for code readability. I second the thoughts of @Mayae.

EDIT: thinking a second time I realize, I probably misunderstood your approach, because a smaller number than FLT_MIN is not representable and therefore does not exist. So it would be a NOP.
If the code you posted is helping and you are testing against a very high negative number, it does sound like a division by zero to me…

2 Likes

Thanks guys.

I copied the value from an assertion failure (I were testing that variable for such small numbers). I tried making a comparison against FLT_MIN before that number and the output sounded distorted. I already have those CPU flags enabled, but they are not helping. There shouldn’t be any division when I’m getting this issue… so I’m a bit clueless.

Thanks daniel. As wrote above, there shouldn’t be any division involved.

I’m just picking up a sample from a circular buffer and there’s a modulation involved. What is VERY strange is that the issue happens only in a precise part of my plugin while in other (involving basically the same way of picking up a sample from the circular buffer) works correctly.

Ok, now I understand. that makes sense for avoiding the symptoms, but finding the cause sounds tricky…
Good luck, if I have an idea I’ll let you know…

e-17 is a very small number. e+17 is a HUGE number… You know, a value with seventeen decimals before the point, ie. -994626945000000000.0
Even ignoring asymmetry (in signal processing you’re dealing with ac signals, so you should always compare against absolute/magnitude values, ie. no sign), you’re checking if the input value is below -900 quadrillions… Unless, as said, you’re dealing with scaling factors, this should always be true (for every negative number). Even if this is by design, I would advise you not to, as you’re losing a lot of precision.

[quote=“daniel, post:3, topic:17779”]EDIT: thinking a second time I realize, I probably misunderstood your approach, because a smaller number than FLT_MIN is not representable and therefore does not exist. So it would be a NOP.If the code you posted is helping and you are testing against a very high negative number, it does sound like a division by zero to me…
[/quote]By definition, FLT_MIN (or std::numerical_limits< float/double>::min()) is the smallest, representable normalized number. So in case of denormals, they will be less (in magnitude) than FLT_MIN.

[quote=“zioaxiom, post:4, topic:17779”]
I’m just picking up a sample from a circular buffer and there’s a modulation involved. What is VERY strange is that the issue happens only in a precise part of my plugin while in other (involving basically the same way of picking up a sample from the circular buffer) works correctly.
[/quote]My guess is your modulation is reading out of bounds, which is why you’re picking up these huge values - it could be random uninitialized memory, or anything that isn’t a float…

1 Like

Ah thanks, I did not know that. You made me read up about denormals, interesting stuff. Took me a while to fully understand, as the examples I found always use decimal system, which has a radix point, but the binary float doesn’t. I think I got it now, when normalizing in binary means maximising the significand. But that’s off topic…

the value that will modulate the reading index is clamped to a valid range before the reading operation. It’s really tricky to find what’s causing that small value. For now I also set the problematic variable as double instead of float and, along with “cleanUnderflow” function, everything is working good.