kernelthread.com

Floating Point Numbers and printk()

Contents

Why floats in the kernel (who floats in the kernel anyway) ?

Why would you want to print floats from within the kernel? All right, if you must, let us assume you are doing something in the kernel that generates floating point values (profiling something, say), and would like to print a result. Note however, that it is not straightforward to do floating point calculations within the kernel in the first place! There may not even be an FPU (sure, there's an FPU emulator but that's no help when you are in the kernel). So we don't bother about the source of floating point numbers in the kernel - we just want to print them. Consider the following snippet:

float x, y, z; unsigned int *u; /* we'll use this later */
x = 3.1415926535; y = 2.7182818284; z = x / y; /* and you've got to print z */

Let us understand why it is difficult to print (via printk or sprintf) the value of z from within the kernel - it's possible, but not quite! On any reasonable hardware (the x86 with FPU as a ubiquitous example), the calculations would be done automagically (that is, you would not have to parse the floats and calculate things yourself, etc - this would be evident to everybody, sure :-). For this discussion, we assume that floats are 32 bits wide (single precision ones are indeed so). First we cook up a scheme to print a representation of z and later get the value of z from it. Next we have a small digression on some floating point woes (or pointed floating woes, if you like).

If you must print floats from within the kernel ...

In the code snippet above, recall that we had declared a variable u as a pointer to an unsigned integer. As should be obvious, the 32 bit float data structure stores the "value" in some format (the IEEE754 Standard). Since *u is also 32 bits wide, let's make u point to the same memory as z. Thus, we do the following:

u = &z; /* u is a pointer to an unsigned int */ printk("%x\n", u); /* print u in hex */

Now, it is clear that printk prints the hex value of the data structure representing the float z. Thus, for z = 6.0, it would print 0x40c00000 (we'll see how in a moment). This way, you can print all the floats you like, and later convert them to a more readable representation with a user level program (a one-line job with Perl, as shown below):

perl -e '$f = 0x40a00000; print unpack("f", pack("I", hex("$f"))), "\n";' /* * Sometimes one fervently wishes that every system * had a Perl interpreter built-in ... perhaps Larry Wall * should think about a PicoPerl chip ... */

This would print 5. If there are many floats to be printed, collect the hex values in a file and use a variation of the above Perl code. By the way, there's nothing special with hexadecimal - you can print them as decimal if you like, and modify the Perl code appropriately.

But why doesn't printk have a "%f" ?

That said and done, perhaps someone would ask why not have a "%f" format identifier in printk ... To appreciate the answer, we look at the IEEE754 floating point standard, from which the single precision (a.k.a float) format is as follows (in other words, this is what the 32 bits of a float variable represent):

value = (-1) ^ s * ((radix) ^ (exponent - bias)) * (1).mantissa

In our case, radix = 2 and bias = 0x7f (127 decimal).

The 32 bits are the hex quantity that we printed above. As the formula for value shows, it takes some effort (and processing) to extract the value from the above 32 bits. 0x40a00000 <=> 5, as I said earlier. Let's see how:

MSB LSB 0x40a00000 <=> 0100 0000 1010 0000 0000 0000 0000 0000

Thus,

s (sign) = 0, (0 => positive, 1 => negative) exponent = 1000001 = 129 (exponent - bias) = 2 value = (-1)^0 * (2)^(2) * (1).mantissa = 4 * (1).mantissa where (1).mantissa => 1.01000000000000000000000 (in binary, sure)

The "1" that has suddenly appeared is hidden or implicit, but has to be there when the value is evaluated. Feel like calculating this? The part after the "." is calculated as follows:

summation(zero_or_one(i) * 2^(-i)) i = 1 to 23 where zero_or_one(i) = { 0 if the ith bit after '.' is 0 { 1 if the ith bit after '.' is 1

So, in this case, the above summation is simply:

2^(-2) = 1/4 = 0.25

Finally (phew),

value = 4 * 1.25 = 5.0

Needless to say, all this stuff belongs to a (user level) library, and not the kernel. And furthermore, there are special cases (the exponent is zero, for example), such as unnormal, denormal and pseudo zeros, which must be taken care of (those not familiar with the standard would be pleased to know that there are representations for Infinity (Inf) and even things that are not numbers (NaN => Not A Number), and for times when the machine thinks you're joking (Ha2N => Ha Ha Nuts?)).

As some indication of the effort involved in printing an ASCII representation of a float, consider that the Floating Point to ASCII Conversion Routine code in [1] runs into as many as fifteen pages, and still is accurate only upto 16 - 18 digits! Hence no printk("%f", ...)! For a better understanding of the IEEE[78]54 FP representation, read the standard, consult the references listed at the end of this document, and for experimenting/playing around, the above shown Perl code can be used in conjunction with this C program, which shows the different constituents (sign, mantissa, ..) of a floating point variable. The code uses C bit-fields, and those unfamiliar with them may consult section 6.9 of [5].

Seeing the internals of a float ...

Download float.c

Further References

Exercises

For those who are bent upon printing a float as ASCII from with the kernel come what may, here's an exercise set:

You may find the following Perl functions helpful while working with different bases and formats:

# decimal to binary sub dectobin { unpack("B32", pack("N", shift)); } # binary to decimal sub bintodec { unpack("N", pack("B32", substr("0" x 32 . shift, -32))); }

Amit Singh