I ran into a curious problem the other day while working on an issue involving the storage, manipulation, and conversion of large number values in Asterisk.
Many moons ago REMB support was added to Asterisk. In order to support a large bit rate the REMB RFC represents the value as mantissa and exponent. Both of these numbers could easily be stored using an unsigned integer type, which is what Asterisk does. The actual calculated bit rate though, mantissa raised to the exponent, easily exceeds the largest number that an unsigned integer can represent. Unfortunately, Asterisk was storing the bit rate as such, thus large values were not being properly portrayed.
Regrettably, a uint64_t is also not sufficiently large enough to store the value. A uint128_t is, however there could be issues with compiler and platform support. Using that data type would very much over complicate things. The obvious alternative here is to simply use a single precision floating point format, a.k.a. a float type (thanks Sean Bright for the suggestion). The precision lost by using a float here is inconsequential. It’s already being lost due to the mantissa and exponent format defined by the specification.
So how does one easily convert between the mantissa and exponent values defined by the REMB message format to a float, and vice versa? Converting to a float is pretty simple as C implicitly handles things for you:
float bitrate = remb.br_mantissa << remb.br_exp;
Doing the reverse, and extracting the mantissa and exponent into two separate values is slightly more involved, but still fairly straightforward:
int exp; frexp(bitrate, &exp); exp = exp > 18 ? exp - 18 : 0; remb.br_mantissa = bitrate / (1 << exp); remb.br_exp = exp;
Luckily, there is a standard function that can be used to easily retrieve the exponent value: frexp. This function also returns the fractional portion. However, since the REMB spec stipulates an 18-bit integer for the mantissa we’ll calculate the exponent to be any value over 18. The mantissa will be the given bit rate divided by two raised to the exponential. This of course could be abstracted to handle any size integer.
Let’s say we have a bit rate of 123456789. Passing this value to the frexp function gives us an ‘exp’ of 27 (i.e. the number of bits needed to represent the given value). Since the mantissa must fit into an 18-bit unsigned integer, and the given bit rate is too large to fit, we must subtract 18 from the exponent. This gives us the number of times the bit rate will fit into that size integer. So 27 – 18 = 9. Now we can get the mantissa that fits into an 18-bit unsigned integer by dividing the bit rate by 2^exp:
mantissa = 123456789.0 / 2^9;
This makes the final mantissa equal to 241126 (implicitly cast), which is less than 262143 (the max value that can be put into an unsigned 18-bit integer). So now we have the following values:
exp = 9; mantissa = 241126;
If we multiply that back we should come up with something close to the original bit rate:
241126 * 2^9 = 123456512;
Precision is lost due to the nature of floating point values. Easier to why from the binary:
241126 * 2^9 == 241126 << 9 /* Or in binary */ 111010110111100110 << 9 = 111010110111100110000000000 = 123456512
Precision on the “lower” end is lost due to zeros being shifted in. Again, this loss is both expected and acceptable in our situation.
The float type is sometimes avoided due to its perceived “slowness”, and lack of precision. However, in some cases like the one here it just makes sense to use one. Using a float in this instance versus an integer type not only simplified the code a bit, but also easily handles the large numeric values potentially described in a REMB message. So float on!