Dạo này thấy trong diễn đàn nhiều bác thắc mắc về số float, hôm nay em xin mạo muội viết một bài về số float, có gì sai sót xin được chỉ giáo.

Như các bạn đã biết, số float là một số thực (số dấu phẩy động), được lưu trữ trên máy tính gồm 4byte, số double thì lưu trữ trên 8 byte. Tuy khác nhau về số lượng byte, nhưng về cách lưu trữ trên máy tính hoàn toàn giống nhau.

Mô hình lưu trữ số float 4byte trên máy tính theo chuẩn IEEE - 754. Cụ thể 32 bit lưu trữ số float như sau:
SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM

S: represents the sign bit where 1 is negative and 0 is positive.
E: is the two’s complement exponent with an offset of 127.
M: is the 23-bit normalized mantissa. The highest bit is always 1 and, therefore, is not stored.
Như vậy ta có:
  • 23 bit đầu tiên(tính từ phải sang trái) lưu trữ phần mantisa (phần sau dấu chấm)
  • 8 bit tiếp theo lưu phần mũ theo offset 127
  • 1 bit cuối cùng lưu dấu

Phần trước dấu chấm (phần nguyên) luôn mang giá trị là "1.", và không được lưu trữ.
Ví dụ:
23 bit mantissa như sau: 00110100001111000100011
8 bit mũ: 10000001 (dec: 129), lấy 129 - 127 ta được số mũ thực sự là 2
1 bit dấu: 0

Theo qui ước ta phải thêm "1." vào trước 23 bit mantissa
1.00110100001111000100011
Sau đó dịch chuyển mantissa theo số mũ 2 ta được
100.110100001111000100011

Ok, tới đây các bạn đã hiểu rõ cơ chế lưu trữ số float 4byte trong máy tính rồi. Giờ chúng ta bắt tay vào công việc chính là lấy giá trị số float (không dùng FPU).
C Code:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4.  
  5. //Function Prototype
  6. int getBit(int num, int n);//Lấy bit n của số num(tính từ bit 0)
  7. void setBit(int *num, int n);//Đặt bit n của số num sang 1
  8. void addV2(char *s1, char *s2);//Cộng 2 số phần sau dấy phẩy động
  9.  
  10. int getV1(char *s, int exp);//Lấy phần trước dấu phẩy động(phần nguyên), lưu trữ trong kiểu int
  11. char* getV2(char *s, int exp);//Lấy phần sau dấu phẩy động(phần phân), trả về trong một string
  12. int getExponent(int num);//Lấy số mũ
  13. char* getMantissa(int num);//Lấy phần mantissa
  14. int getSign(int num);//Lấy dấu
  15. void truncrs(char *s);//Làm tròn phần thập phân, lấy 6 số có nghĩa sau dấu phẩy
  16.  
  17.  
  18. int getBit(int num, int n)
  19. {
  20.     return (num&(1<<n))==0?0:1;
  21. }
  22.  
  23. void setBit(int *num, int n)
  24. {
  25.     *num|=(1<<n);
  26. }
  27.  
  28. void addV2(char *s1, char *s2)
  29. {
  30.     int i, len1, len2, n;
  31.    
  32.     len1=strlen(s1);
  33.     len2=strlen(s2);
  34.     n=0;
  35.  
  36.     if (len2<len1) len1=len2;
  37.     for (i=len1-1; i>=0; i--)
  38.     {
  39.         s1[i]+=s2[i]+n-48;
  40.         n=0;
  41.        
  42.         if (s1[i]>'9')
  43.         {
  44.             s1[i]-=10;
  45.             n=1;
  46.         }
  47.     }
  48. }
  49.  
  50. int getSign(int num)
  51. {
  52.     return getBit(num, 31);
  53. }
  54.  
  55. int getExponent(int num)
  56. {
  57.     int r, i;
  58.    
  59.     r=0;
  60.     for (i=23; i<=30; i++)
  61.         if (getBit(num, i)==1) setBit(&r, i-23);
  62.        
  63.     return r-127;
  64. }
  65.  
  66. char* getMantissa(int num)
  67. {
  68.     int i, start, exp;
  69.     char *s;
  70.    
  71.     s=(char*)malloc(25);
  72.     memset((void*)s, '0', 25);
  73.     s[24]=0;
  74.     exp=getExponent(num);
  75.     start=(exp<0?-exp:0);//start lưu phần bit được dịch chuyển của số mũ để khớp với 23bit
  76.    
  77.     for (i=start; i<=22; i++)//Lấy phần bit được dịch chuyển theo số mũ
  78.     {
  79.         s[i-start]=getBit(num, i)+'0';
  80.     }
  81.     if (23-start>=0) s[23-start]='1';//Thêm bit 1 vào trước phần đã được dịch chuyển
  82.    
  83.     return s;
  84. }
  85.  
  86. int getV1(char *mant, int exp)
  87. {
  88.     int i, start, r;
  89.    
  90.     r=0;
  91.     if (exp<0)//Lấy số bit thuộc phần nguyên
  92.     {
  93.         start=24+exp;
  94.     }
  95.     else start=23-exp;
  96.    
  97.     for (i=start; i<=23; i++)//Duyệt các bit thuộc phần nguyên
  98.     {
  99.         if (i>=0 && mant[i]=='1')
  100.         {
  101.             if (i-start>=0 && i-start<32) setBit(&r, i-start);//Lấy giá trị phần nguyên
  102.         }
  103.     }
  104.    
  105.     return r;
  106. }
  107.  
  108. char* getV2(char *mant, int exp)
  109. {
  110.     char *v[] = {//Khai báo trước các giá trị 2^-1 tới 2^-23 để thuận tính toán
  111.         "5",//1
  112.         "25",//2
  113.         "125",//3
  114.         "0625",//4
  115.         "03125",//5
  116.         "015625",//6
  117.         "0078125",//7
  118.         "00390625",//8
  119.         "001953125",//9
  120.         "0009765625",//10
  121.         "00048828125",//11
  122.         "000244140625",//12
  123.         "0001220703125",//13
  124.         "00006103515625",//14
  125.         "000030517578125",//15
  126.         "0000152587890625",//16
  127.         "00000762939453125",//17
  128.         "000003814697265625",//18
  129.         "0000019073486328125",//19
  130.         "00000095367431640625",//20
  131.         "000000476837158203125",//21
  132.         "0000002384185791015625",//22
  133.         "00000011920928955078125",//23
  134.     };
  135.     char *r;
  136.     int i, start;
  137.    
  138.     r=(char*)malloc(24);
  139.     memset((void*)r, '0', 24);
  140.     r[23]=0;
  141.    
  142.     if (exp<0)//Lấy số bit thuộc phần thập phân
  143.         start=23-exp;
  144.     else
  145.         start=22-exp;
  146.    
  147.     if (start>22) start=22;
  148.     for (i=start; i>=0; i--)
  149.     {
  150.         if (mant[i]=='1')//Nếu bit i bằng 1 thì cộng thêm vào kết quả phần thập phân tương ứng
  151.             addV2(r, v[start-i]);
  152.     }
  153.  
  154.     return r;
  155. }
  156.  
  157. void truncrs(char *s)//Hàm làm tròn để in ra màn hình
  158. {
  159.     int dot, i, len, n;
  160.    
  161.     len=strlen(s);
  162.     dot=0;
  163.     n=0;
  164.    
  165.     while (s[dot]!='.') dot++;
  166.     if (dot+7>=len) return;
  167.    
  168.     for (i=len-1; i>=dot+7; i--)
  169.     {
  170.         if (s[i]+n>'4')
  171.             n=1;
  172.         else
  173.             n=0;
  174.         s[i]='0';
  175.     }
  176.    
  177.     s[dot+7]=0;
  178.     if (n==0) return;
  179.    
  180.     for (i=dot+6; i>=0; i--)
  181.     {
  182.         if (s[i]=='.') continue;
  183.        
  184.         s[i]+=1;
  185.        
  186.         if (s[i]>'9')
  187.             s[i]-=10;
  188.         else break;
  189.     }
  190. }
  191.  
  192. int main()
  193. {
  194.     float a=5.22;
  195.     int p;
  196.     char *mant, *v2;
  197.     char kq[255];
  198.     int nkq, exp, v1;
  199.  
  200.     p=*(int*)&a;//Lấy 4byte của số float a
  201.     nkq=0;
  202.    
  203.     printf("Value: %f\n", a);
  204.     if (getSign(p)==1)
  205.     {
  206.         kq[nkq]='-';
  207.         nkq++;
  208.     }
  209.     exp=getExponent(p);
  210.     mant=getMantissa(p);
  211.     v1=getV1(mant, exp);
  212.     v2=getV2(mant, exp);
  213.     sprintf(kq+nkq, "%u.%s", v1, v2);
  214.     printf("Real: %s\n", kq);
  215.     truncrs(kq);
  216.     printf("Trunc: %s\n", kq);
  217.    
  218.    
  219.     free(mant);
  220.     free(v2);
  221.     return 0;
  222. }

Tạm thời đoạn code trên xử lý phần nguyên chỉ tới giới hạn của số nguyên 4byte vì trả về theo kiểu int, nếu muốn xử lý lớn hơn cần dùng số nguyên 64bit, hoặc dùng số lớn để giải quyết.

Hôm nào rảnh mình sẽ post nốt phần nhập 1 số float và chuyển sang lưu trữ trên 4byte.
Cám ơn các bạn đã quan tâm theo dõi.