Search the Asterisk Blog

Developer Notes: Large Number Storage

By Kevin Harwell

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.

Background

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.

Solution

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.

Implementation

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:

Doing the reverse, and extracting the mantissa and exponent into two separate values is slightly more involved, but still fairly straightforward:

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.

Example

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:

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:

If we multiply that back we should come up with something close to the original bit rate:

Precision is lost due to the nature of floating point values. Easier to why from the binary:

Precision on the “lower” end is lost due to zeros being shifted in. Again, this loss is both expected and acceptable in our situation.

Float On

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!

There Are 2 Comments

Add to the Discussion

Your email address will not be published. Required fields are marked *

About the Author

Kevin Harwell

Kevin is a Software Developer at Digium. He has a diverse background in the software industry and has worked on an assortment of projects. Since joining the Asterisk team a few years ago he has been a frequent contributor to a variety of areas within the project. He also can usually be seen with a cup of hot tea.

See All of Kevin's Articles