Normalize integer types to float range I wrote two template functions to take an integer type and output a float in the given range. One function takes signed integers and the other takes unsigned integers.

The functions are used to normalize audio PCM data into a range that an encoder is expecting, such as [0.0f, 1.0f]. The source audio data can come in many forms (signed, unsigned, 1 byte, 2 bytes, etc) so I thought template functions would be ideal for this.

The caller has the responsibility of calling the function with an appropriately sized type containing the sample to normalize (e.g. int16_t). The functions use a lookup-table to determine the max/min values the type can contain based on the size of the integer type.

util.cc

#include &lt;stdint.h&gt;

const uintmax_t MAX_VALUE_PER_BYTES_UNSIGNED[] = {
    0,
    uintmax_t(UINT8_MAX),
    uintmax_t(UINT16_MAX),
    uintmax_t(UINT16_MAX) + uintmax_t(UINT8_MAX),
    uintmax_t(UINT32_MAX),
    uintmax_t(UINT32_MAX) + uintmax_t(UINT8_MAX),
    uintmax_t(UINT32_MAX) + uintmax_t(UINT16_MAX),
    uintmax_t(UINT32_MAX) + uintmax_t(UINT16_MAX) + uintmax_t(UINT8_MAX),
    uintmax_t(UINT64_MAX)
};

const intmax_t MAX_VALUE_PER_BYTES_SIGNED[] = {
    0,
    intmax_t(INT8_MAX),
    intmax_t(INT16_MAX),
    intmax_t(INT16_MAX) + intmax_t(INT8_MAX),
    intmax_t(INT32_MAX),
    intmax_t(INT32_MAX) + intmax_t(INT8_MAX),
    intmax_t(INT32_MAX) + intmax_t(INT16_MAX),
    intmax_t(INT32_MAX) + intmax_t(INT16_MAX) + intmax_t(INT8_MAX),
    intmax_t(INT64_MAX)
};

const intmax_t MIN_VALUE_PER_BYTES_SIGNED[] = {
    0,
    intmax_t(INT8_MIN),
    intmax_t(INT16_MIN),
    intmax_t(INT16_MIN) + intmax_t(INT8_MIN),
    intmax_t(INT32_MIN),
    intmax_t(INT32_MIN) + intmax_t(INT8_MIN),
    intmax_t(INT32_MIN) + intmax_t(INT16_MIN),
    intmax_t(INT32_MIN) + intmax_t(INT16_MIN) + intmax_t(INT8_MIN),
    intmax_t(INT64_MIN)
};


util.h

#include &lt;stdint.h&gt;
#include &lt;limits&gt;
#include &lt;cmath&gt;

extern const uintmax_t MAX_VALUE_PER_BYTES_UNSIGNED[];
extern const intmax_t MAX_VALUE_PER_BYTES_SIGNED[];
extern const intmax_t MIN_VALUE_PER_BYTES_SIGNED[];

template &lt;typename T&gt;
float normalizeToRangeFloatSigned(T num, float rangeMin, float rangeMax) {
    float f = 0.0f;

    if (!std::numeric_limits&lt;T&gt;::is_integer) {
        return f;
    }

    const float rangeDiff = rangeMax - rangeMin;
    if (!rangeDiff || rangeDiff &lt; 0.0f) {
        return f;
    }

    uintmax_t uintRangeMax = MAX_VALUE_PER_BYTES_UNSIGNED[sizeof(T)];

    const intmax_t tRangeMin = MIN_VALUE_PER_BYTES_SIGNED[sizeof(T)];
    const intmax_t tRangeMax = MAX_VALUE_PER_BYTES_SIGNED[sizeof(T)];
    const intmax_t tRangeMinAbs = std::abs(tRangeMin);
    float percent = 0.0f;

    if (num &lt; 0) {
        if (num == tRangeMin) {
            percent = 0.0f;
        } else {
            percent = (tRangeMinAbs - T{-1} * num) / float(uintRangeMax);
        }
    } else if (!num) {
        percent = 0.5f;
    } else {
        if (num == tRangeMax) {
            percent = 1.0f;
        } else {
            percent = (num + tRangeMinAbs) / float(uintRangeMax);
        }
    }

    f = (percent) * rangeDiff + rangeMin;

    return f;
}

template &lt;typename T&gt;
float normalizeToRangeFloatUnsigned(T num, float rangeMin, float rangeMax) {
    float f = 0.0f;

    if (!std::numeric_limits&lt;T&gt;::is_integer) {
        return f;
    }

    const float rangeDiff = rangeMax - rangeMin;
    if (!rangeDiff || rangeDiff &lt; 0.0f) {
        return f;
    }

    uintmax_t uintRangeMax = MAX_VALUE_PER_BYTES_UNSIGNED[sizeof(T)];
    f = (num / float(uintRangeMax)) * rangeDiff + rangeMin;

    return f;
}


I don't have much experience with templates so any feedback is welcome. Also, any improvements to the functions time-efficiency.
&lt;limits&gt; already provides a set of limits on values that can be stored in the various types. It's also a template, so it's easy to invoke on a template parameter. As a quick demo of that particular part of things:

template &lt;class T&gt;
void showminmax(T) { 
    std::cout &lt;&lt; "min: " &lt;&lt; std::numeric_limits&lt;T&gt;::min() &lt;&lt; 
               "\nmax: " &lt;&lt; std::numeric_limits&lt;T&gt;::max();
}

int main() { 
     std::cout &lt;&lt; "int\n";
     showminmax(1);

     std::cout &lt;&lt; "\nunsigned long long\n";
     showminmax(1ULL);
}


I believe the code to normalize the number to the range [0..1] can be simplified quite a bit as well, to something on this order:

template &lt;class T&gt;
float normalize(T t) {
    static_assert(std::is_integral&lt;T&gt;::value, "Input must be integral");
    float min = std::numeric_limits&lt;T&gt;::min();
    float max = std::numeric_limits&lt;T&gt;::max();
    float range = max - min;

    return (t - min) / range;
}


This should work for either signed or unsigned types. Here's a quick bit of demo code to exercise it, and show the results:

int main() {
    std::cout &lt;&lt; "char(min):   " &lt;&lt; normalize(std::numeric_limits&lt;char&gt;::min()) &lt;&lt; "\n";
    std::cout &lt;&lt; "char (1/4):  " &lt;&lt; normalize(char(std::numeric_limits&lt;char&gt;::min() &gt;&gt; 1)) &lt;&lt; "\n";
    std::cout &lt;&lt; "char (0):    " &lt;&lt; normalize('\0') &lt;&lt; "\n";
    std::cout &lt;&lt; "char (3/4):  " &lt;&lt; normalize(char(std::numeric_limits&lt;char&gt;::max() &gt;&gt; 1)) &lt;&lt; "\n";
    std::cout &lt;&lt; "char (max):  " &lt;&lt; normalize(std::numeric_limits&lt;char&gt;::max()) &lt;&lt; "\n\n";

    std::cout &lt;&lt; "int(min):   " &lt;&lt; normalize(std::numeric_limits&lt;int&gt;::min()) &lt;&lt; "\n";
    std::cout &lt;&lt; "int (1/4):  " &lt;&lt; normalize(std::numeric_limits&lt;int&gt;::min() &gt;&gt; 1) &lt;&lt; "\n";
    std::cout &lt;&lt; "int (0):    " &lt;&lt; normalize(0) &lt;&lt; "\n";
    std::cout &lt;&lt; "int (3/4):  " &lt;&lt; normalize(std::numeric_limits&lt;int&gt;::max() &gt;&gt; 1) &lt;&lt; "\n";
    std::cout &lt;&lt; "int (max):  " &lt;&lt; normalize(std::numeric_limits&lt;int&gt;::max()) &lt;&lt; "\n\n";

    std::cout &lt;&lt; "uint (0):   " &lt;&lt; normalize(0U) &lt;&lt; "\n";
    std::cout &lt;&lt; "uint (1/4): " &lt;&lt; normalize(std::numeric_limits&lt;unsigned int&gt;::max() / 4) &lt;&lt; "\n";
    std::cout &lt;&lt; "uint (mid): " &lt;&lt; normalize(std::numeric_limits&lt;unsigned int&gt;::max() / 2) &lt;&lt; "\n";
    std::cout &lt;&lt; "uint (max): " &lt;&lt; normalize(std::numeric_limits&lt;unsigned int&gt;::max()) &lt;&lt; "\n\n";


    std::cout &lt;&lt; "ULL (0):   " &lt;&lt; normalize(0ULL) &lt;&lt; "\n";
    std::cout &lt;&lt; "ULL (1/4): " &lt;&lt; normalize(std::numeric_limits&lt;unsigned long long&gt;::max() / 4) &lt;&lt; "\n";
    std::cout &lt;&lt; "ULL (mid):  " &lt;&lt; normalize(std::numeric_limits&lt;unsigned long long&gt;::max() / 2) &lt;&lt; "\n";
    std::cout &lt;&lt; "ULL (max):  " &lt;&lt; normalize(std::numeric_limits&lt;unsigned long long&gt;::max()) &lt;&lt; "\n";
}


Note that twos complement is asymmetric, so the values produced are technically just a tiny bit off (well, off from what you might expect, anyway). This will normally be lost in the rounding for any type with more than ~24 input bits, but if the input type is smaller than that (e.g., most implementations of char and short) a signed value of 0 won't give precisely 0 as the result (unless you have a machine with something like signed-magnitude or ones complement int, anyway).