问题
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
?
回答1:
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 2
1
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 2
1
. 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:
- 3.2/21 = 1.6
- Take integral part from 1.6 to obtain the hex-digit
1
before decimal point.- Multiply
.6
by16
. 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.
回答2:
This is an easy question to answer.
The obvious explanation is that (1 + 9.0/16 + 9.0/(16*16) + 9.0/(16*16*16) + 9.0/(16*16*16*16) + ...)*2 = 3.2
You can easily verify this by taking the five first terms and write this to a Python interpreter: (1 + 9.0/16 + 9.0/(16*16) + 9.0/(16*16*16) + 9.0/(16*16*16*16))*2
The answer is 3.199981689453125.
Why the 2 at the end, then? Of course, because (1<<1) = 2 and the number after the "p" was +1. If you would have +2, then you would use (1<<2) = 4 instead as the multiplier.
EDIT: Ok, so an opposite algorithm was needed.
First, find the shift amount x for which (3.2/(2^x)) is between 1 and 2. In this case, 3.2/(2^1) = 3.2/2 = 1.6.
Then, output "1." and subtract 1 from the result, giving 0.6
Then, multiply the result by 16 and take the integer part. The product is 9.6. Output is "9".
Then, subtract the output from the result. 9.6 - 9 = 0.6.
Repeat: multiply the result by 16 and take the integer part. The product is 9.6. Output is "9".
Again, subtract the output from the result. 9.6 - 9 = 0.6
Repeat these processes. Ad infinitum, if you want the full expansion, but in practice you would stop the iteration somewhere. At the end, you output "p" and "+1" because the power was +1. The "a" near the end occurs due to limited floating point calculation precision.
Another edit: to fully understand what effects the limited precision is going to have, you can read "What Every Computer Scientist Should Know About Floating-Point Arithmetic", a very famous paper that has an online version e.g. there: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
An example for the algorithm for the number 0.1: we find that 0.1/(2^(-4)) = 0.1*2^4 = 0.1*16 = 1.6 which is between 1 and 2 so we choose the power -4. So, we output "1." and then calulate (1.6-1)=0.6 and multiply it by 16: 16*0.6 = 9.6. So, we output "9", subtract 9.6 - 9 = 0.6 and multiply again it by 16, giving another "9". And so on. So, the answer is "1.9999999....p-4" given unlimited floating point precision. But, due to limited precision, it appears the last letter is "a". So, the algorithm also works for negative powers but then you have to note that dividing by a negative power is the same as multiplying by a positive power. So, you need to consider negative numbers as well in selecting the power which gives the value between 1 and 2.
来源:https://stackoverflow.com/questions/29143651/how-a-conversion-work-in-printf-statement