Search code examples
cnumbersbit-manipulationclamp

Fastest way to clamp a real (fixed/floating point) value?


Is there a more efficient way to clamp real numbers than using if statements or ternary operators? I want to do this both for doubles and for a 32-bit fixpoint implementation (16.16). I'm not asking for code that can handle both cases; they will be handled in separate functions.

Obviously, I can do something like:

double clampedA;
double a = calculate();
clampedA = a > MY_MAX ? MY_MAX : a;
clampedA = a < MY_MIN ? MY_MIN : a;

or

double a = calculate();
double clampedA = a;
if(clampedA > MY_MAX)
    clampedA = MY_MAX;
else if(clampedA < MY_MIN)
    clampedA = MY_MIN;

The fixpoint version would use functions/macros for comparisons.

This is done in a performance-critical part of the code, so I'm looking for an as efficient way to do it as possible (which I suspect would involve bit-manipulation)

EDIT: It has to be standard/portable C, platform-specific functionality is not of any interest here. Also, MY_MIN and MY_MAX are the same type as the value I want clamped (doubles in the examples above).


Solution

  • For the 16.16 representation, the simple ternary is unlikely to be bettered speed-wise.

    And for doubles, because you need it standard/portable C, bit-fiddling of any kind will end badly.

    Even if a bit-fiddle was possible (which I doubt), you'd be relying on the binary representation of doubles. THIS (and their size) IS IMPLEMENTATION-DEPENDENT.

    Possibly you could "guess" this using sizeof(double) and then comparing the layout of various double values against their common binary representations, but I think you're on a hiding to nothing.

    The best rule is TELL THE COMPILER WHAT YOU WANT (ie ternary), and let it optimise for you.

    EDIT: Humble pie time. I just tested quinmars idea (below), and it works - if you have IEEE-754 floats. This gave a speedup of about 20% on the code below. IObviously non-portable, but I think there may be a standardised way of asking your compiler if it uses IEEE754 float formats with a #IF...?

      double FMIN = 3.13;
      double FMAX = 300.44;
    
      double FVAL[10] = {-100, 0.23, 1.24, 3.00, 3.5, 30.5, 50 ,100.22 ,200.22, 30000};
      uint64  Lfmin = *(uint64 *)&FMIN;
      uint64  Lfmax = *(uint64 *)&FMAX;
    
        DWORD start = GetTickCount();
    
        for (int j=0; j<10000000; ++j)
        {
            uint64 * pfvalue = (uint64 *)&FVAL[0];
            for (int i=0; i<10; ++i)
                *pfvalue++ = (*pfvalue < Lfmin) ? Lfmin : (*pfvalue > Lfmax) ? Lfmax : *pfvalue;
        }
    
        volatile DWORD hacktime = GetTickCount() - start;
    
        for (int j=0; j<10000000; ++j)
        {
            double * pfvalue = &FVAL[0];
            for (int i=0; i<10; ++i)
                *pfvalue++ = (*pfvalue < FMIN) ? FMIN : (*pfvalue > FMAX) ? FMAX : *pfvalue;
        }
    
        volatile DWORD normaltime = GetTickCount() - (start + hacktime);