Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How %a conversion work in printf statement?

Maybe this doesn't belong on SO but I don't know where else.

I have to reimplement printf(3) with C without using any function that would do the conversion for me, I'm nearly done, but I'm stuck on %a, I really don't understand what is happening here for example:

printf("%a\n", 3.0); //#=> 0x1.8p+1
printf("%a\n", 3.1); //#=> 0x1.8cccccccccccdp+1
printf("%a\n", 3.2); //#=> 0x1.999999999999ap+1
printf("%a\n", 3.3); //#=> 0x1.a666666666666p+1
printf("%a\n", 3.4); //#=> 0x1.b333333333333p+1
printf("%a\n", 3.5); //#=> 0x1.cp+1
printf("%a\n", 3.6); //#=> 0x1.ccccccccccccdp+1

Of course I read the man which says:

The double argument is rounded and converted to hexadecimal notation in the style[-]0xh.hhhp[+-]d, where the number of digits after the hexadecimal-point character is equal to the precision specification.

But this doesn't really help I don't understand the process that transforms 3.2 to 1.999999999999ap+1

I don't need any code but really more an explanation.

PS: If this isn't the place for this question could you point me to the right place?

EDIT: While @juhist answer works for numbers >= 1.0 it doesn't explain how to get the result for numbers between 0.0 et 1.0:

printf("%a\n", 0.01); //#=> 0x1.47ae147ae147bp-7
printf("%a\n", 0.1);  //#=> 0x1.999999999999ap-4
printf("%a\n", 0.2);  //#=> 0x1.999999999999ap-3
printf("%a\n", 0.3);  //#=> 0x1.3333333333333p-2
printf("%a\n", 0.4);  //#=> 0x1.999999999999ap-2
printf("%a\n", 0.5);  //#=> 0x1p-1
printf("%a\n", 0.6);  //#=> 0x1.3333333333333p-1

Also I would really like a precision on this The "a" near the end occurs due to limited floating point calculation precision concerning the conversion: printf("%a\n", 3.2); //#=> 0x1.999999999999ap+1

EDIT2: Now the last mystery is to explain why in this case:

printf("%a\n", 0.1); //#=> 0x1.999999999999ap-4

The last 9 becomes and a and in this case:

printf("%a\n", 0.3); //#=> 0x1.3333333333333p-2

The last 3 stays a 3?

like image 882
ItsASecret Avatar asked Mar 19 '15 11:03

ItsASecret


1 Answers

First you need to know what the representation 0xh.hhhh p±d, mean? Let's understand it by taking an example of hexadecimal constant 0x1.99999ap+1.
The digit 1 before the decimal point is a hex digit and the number of hexadecimal digits after it (99999a) is equal to the precision. 0x is the hex introducer and the p is exponent field. The exponent is a decimal number that indicates the power of 2 by which the significant part is multiplied.

So, when 0x1.99999ap+1 will be multiplied with 21 then it will be converted to 3.2 in decimal. Recall that how 1.55e+1 converted to 15.500000 in decimal. Similar thing is happening here.

Now you need to know the mathematics behind the conversion of 0x1.99999ap+1 to 3.2. This will proceed as follows

1*160 + 9*16-1 + 9*16-2 + 9*16-3 + 9*16-4 + 9*16-5 + 10*16-1

Which is (in decimal) equal to

 1.60000002384185791015625  

You need only up to 1 precision. So, take 1.6 and multiply it with 21. Which will give 3.2.

To go to the reverse of the above process you need to find power of 2 by which the floating point number will be divided to obtain the digit 1 before decimal point. After that use successive multiplications to change the fractional part to hexadecimal fraction. Proceed as follows:

  1. 3.2/21 = 1.6
  2. Take integral part from 1.6 to obtain the hex-digit 1 before decimal point.
  3. Multiply .6 by 16. The the integral part obtained will becomes a numeral in the hexadecimal fraction. Repeat this step with the obtained fractional part to the desired precision (6 is default).
    • .6 * 16 = 9.6 ---> Integral part = 9 Fractional part = .6
    • .6 * 16 = 9.6 ---> Integral part = 9 Fractional part = .6
    • .6 * 16 = 9.6 ---> Integral part = 9 Fractional part = .6
    • .6 * 16 = 9.6 ---> Integral part = 9 Fractional part = .6
    • .6 * 16 = 9.6 ---> Integral part = 9 Fractional part = .6
    • .6 * 16 = 9.6 ---> Integral part = 9 Fractional part = .6

So the hexadecimal fraction will become .999999 Now combine hex indicator 0x, hex-digit before decimal point and the hexadecimal fraction obtained along with exponent field to get the result.

3.210 = 0x1.999999p+116

Similarly you can obtain hexadecimal floating point number for numbers less than 1.0., for example 0.01. In this case to obtain the hex-digit 1 before decimal point you need to divide it with a number which is power of 2. Since 128 (25) after multiplying with 0.01 will give the number whose integral part becomes 1, 128*.01 = 1.28. It means you need to multiply 0.01 by 1/2-5 or you can say you need to divide it by 2-5 to get 1.28. Now apply the steps 2 and 3 stated above.

like image 186
haccks Avatar answered Oct 19 '22 03:10

haccks