关于C中浮点数(float,double)类型的思考?

Really_want 2017-11-13 01:47:14
先看问题:
问题:为什么Double类型和Float类型表现不太一样?预期:sizeof(Float)=4,sizeof(Double)=8.
补充:Float类型正如预期的,大小为4字节;但Double类型却有12字节大小(如果不加pack(1)设置则为16)。
---------------------------------------------------------------------------------------------------------------------------------------------
"Talk is chip. Show me the code!"


#include <cstdio> //printf
#include <cstring> //memset

typedef unsigned char byte_t;

const int BIT_NUM = 8; // bit number of one byte.

/*
从下标为@start的开始位置输出字节@byte的@num数量的标记(1/0)
(01234567)
例如: @byte: 00101001
@start: 1
@num: 4
输出: 0101
*/
int print_byte_bits(byte_t byte, int start, int num)
{
int i,n;
if(start < 0) start = 0;
if(num - start > BIT_NUM) num = BIT_NUM - start;
n = 0;
for(i=start; i < BIT_NUM && n < num; i++)
{
printf("%d",((byte & (0x80>>i)) ? 1 : 0)); //0x80: 1000 0000b
n++;
}
return n;
}

/*
从下标为@start的开始位置输出任意位置@addr的@num数量的标记(1/0)
(0123456789012345)
例如: @addr: 0010100100011010
^^^^^^^
@start: 5
@num: 7
输出: 0010001
*/
int print_bits(const void* addr, int start, int num)
{
int ibyte,ibit,n,temp;
byte_t byte;
if(start < 0) start = 0;
n = 0;
for(ibyte = start / BIT_NUM, ibit = start % BIT_NUM; n < num; ibyte++)
{
byte = ((byte_t*)addr)[ibyte];
temp = print_byte_bits(byte, ibit, num - n);
if(temp == 0) break;
n += temp;
ibit = 0;
}
return n;
}

//检测当前系统是否为“大端”字节序
/*bool*/int bigendian()
{
const unsigned short s = 0x0201;
struct S{ byte_t b1,b2; }* p = (struct S*)&s;
// struct: b1 b2
// big: [0x02 0x01]
// little: [0x01 0x02]
return p->b1 > p->b2;
}

/*
按字节反转从@p指定位置开始@len指定长度的内容
例如: @p: 0x [01 02 03 04] 05 06
@len: 4
反转后: 0x [04 03 02 01] 05 06
*/
void reverse(void* p, int len)
{
int i;
byte_t t;
byte_t* byte = (byte_t*)p;
for(i = 0; i < len/2; i++)
{
t = byte[i];
byte[i] = byte[(len-1)-i];
byte[(len-1)-i] = t;
}
}

//测试1:输出int类型的位(bits)
void t1()
{
int i = 0x01020304;
size_t j;
printf("int 0x%08x: ",i);
if(!bigendian())
reverse(&i,sizeof(i));
for(j=0; j<sizeof(i); j++)
{
print_bits(&i,j*BIT_NUM,BIT_NUM);
printf(" ");
}
printf("\n");
//output: 00000001 00000010 00000011 00000100
}

//测试2:输出float类型的位
// float: [1:sign][8:exponent][23:mantissa]
typedef struct _Float
{
int mantissa:23;
int exponent:8;
int sign:1;
} Float;
void t2()
{
float f = 8.25; //: 8.25(10) = 1000.01(2) = 1.00001 * 2^3
printf("float %f: ",f);
if(!bigendian())
reverse(&f,sizeof(f));
print_bits(&f,0,1);
printf(" ");
print_bits(&f,1,8);
printf(" ");
print_bits(&f,9,23);
printf("\n");
//output: 0 10000010 00001000000000000000000
// / ^^^^^
// 127+3 0.00001
// //
// 127(10) = 11111111(2)
}

//测试3:输出double类型的位
// double: [1:sign][11:exponent][52:mantissa]
#pragma pack(push)
#pragma pack(1)
typedef struct _Double
{
long long mantissa:52; //long long -> 64 bits
int exponent:11;
int sign:1;
} Double;
#pragma pack(pop)
void t3()
{
double f = 8.25;
printf("double %f: ",f);
if(!bigendian())
reverse(&f,sizeof(f));
print_bits(&f,0,1);
printf(" ");
print_bits(&f,1,11);
printf(" ");
print_bits(&f,12,52);
printf("\n");
//output: 0 10000000010 0000100000000000000000000000000000000000000000000000
}

//模版:输出任意类型的位
template<typename T>
void print(T d)
{
if(!bigendian()) reverse(&d,sizeof(d));
for(size_t i=0; i<sizeof(d); i++)
{
print_bits(&d,i*BIT_NUM,BIT_NUM);
printf(" ");
}
printf("\n");
}

//测试4:输出自定义类型的大小
void t4()
{
printf("Float:%d, Double:%d\n",sizeof(Float),sizeof(Double));
}

//测试5:查看float类型各个"区段"的位置
void t5()
{
Float f;
memset(&f,0,sizeof(f));
f.sign = -1;
printf("s i g n:"); print(f);
memset(&f,0,sizeof(f));
f.exponent = -1;
printf("exponent:"); print(f);
memset(&f,0,sizeof(f));
f.mantissa = -1;
printf("mantissa:"); print(f);
}

//测试6:查看double类型各个"区段"的位置
void t6()
{
Double d;
memset(&d,0,sizeof(d));
d.sign = -1;
printf("s i g n:"); print(d);
memset(&d,0,sizeof(d));
d.exponent = -1;
printf("exponent:"); print(d);
memset(&d,0,sizeof(d));
d.mantissa = -1;
printf("mantissa:"); print(d);
}

//测试7:通过自定义类型"构造"一个float类型的小数
void t7()
{
Float f;
memset(&f,0,sizeof(f));
// 1.00001(2) * 2^3 = 8.25(10)
f.sign = 0;
f.exponent = 127+3; //:3
f.mantissa = 0x040000;//:.00001 -> 0000100 00000000 00000000
float* p = (float*)&f;
printf("Float(0,130,0x040000):\n");
printf("(2): ");print(f);
printf("(10): %f\n",*p);
}

int main()
{
printf("测试1:输出int类型的位(bits)\n"); t1();
printf("测试2:输出float类型的位\n"); t2();
printf("测试3:输出double类型的位\n"); t3();
printf("测试4:输出自定义类型的大小\n"); t4();
printf("测试5:查看float类型各个'区段'的位置\n"); t5();
printf("测试6:查看double类型各个'区段'的位置\n"); t6();
printf("测试7:通过自定义类型'构造'一个float类型的小数\n"); t7();
printf("问题:为什么Double类型和Float类型表现不太一样?预期:sizeof(Float)=4,sizeof(Double)=8.\n");
printf("补充:Float类型正如预期的,大小为4字节;但Double类型却有12字节大小(如果不加pack(1)设置则为16)。\n");
return 0;
}

/*
测试1:输出int类型的位(bits)
int 0x01020304: 00000001 00000010 00000011 00000100
测试2:输出float类型的位
float 8.250000: 0 10000010 00001000000000000000000
测试3:输出double类型的位
double 8.250000: 0 10000000010 0000100000000000000000000000000000000000000000000000
测试4:输出自定义类型的大小
Float:4, Double:12
测试5:查看float类型各个'区段'的位置
s i g n:10000000 00000000 00000000 00000000
exponent:01111111 10000000 00000000 00000000
mantissa:00000000 01111111 11111111 11111111
测试6:查看double类型各个'区段'的位置
s i g n:00000000 00000000 00001000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
exponent:00000000 00000000 00000111 11111111 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
mantissa:00000000 00000000 00000000 00000000 00000000 00001111 11111111 11111111 11111111 11111111 11111111 11111111
测试7:通过自定义类型'构造'一个float类型的小数
Float(0,130,0x040000):
(2): 01000001 00000100 00000000 00000000
(10): 8.250000
问题:为什么Double类型和Float类型表现不太一样?预期:sizeof(Float)=4,sizeof(Double)=8.
补充:Float类型正如预期的,大小为4字节;但Double类型却有12字节大小(如果不加pack(1)设置则为16)。
*/

// 参考文章:
// http://blog.csdn.net/wuna66320/article/details/1691734
...全文
510 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
Really_want 2017-11-14
  • 打赏
  • 举报
回复
引用 18 楼 paschen 的回复:
typedef struct _Double { long long mantissa:52; //long long -> 64 bits int exponent:11; int sign:1; } Double; 改成 typedef struct _Double { long long mantissa:52; //long long -> 64 bits long long exponent:11; long long sign:1; } Double;
CT8100 2017-11-13
  • 打赏
  • 举报
回复
引用 16 楼 zhao4zhong1 的回复:
[/code]
大致明白了~。谢谢四爷。 之前进入了字符长度固定的误区了。尴尬
赵4老师 2017-11-13
  • 打赏
  • 举报
回复
叒供参考:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
int main() {
    int i,v;
    char bs[33];
    char b[33];
    char hs[9];
    char h[9];
    char s[4];
    char *e;

// 十进制整数转二进制串;
    i=1024;
    ltoa(i,b,2);
    sprintf(bs,"%032s",b);
    printf("i=%d,bs=%s\n",i,bs);
// 十进制整数转十六进制串;
    i=1024;
    ltoa(i,h,16);
    sprintf(hs,"%08s",h);
    printf("i=%d,hs=%s\n",i,hs);
// 十六进制字符串转成十进制数
    strcpy(hs,"00000400");
    sscanf(hs,"%x",&i);
    printf("hs=%s,i=%d\n",hs,i);
// 二进制字符串转化为十六进制字符串;
    strcpy(bs,"00000000000000000000010000000000");
    i=strtol(bs,&e,2);
    ltoa(i,h,16);
    sprintf(hs,"%08s",h);
    printf("bs=%s,hs=%s\n",bs,hs);
// 二进制字符串转化为十进制数;
    strcpy(bs,"00000000000000000000010000000000");
    i=strtol(bs,&e,2);
    printf("bs=%s,i=%d\n",bs,i);
// 十六进制字符串转成二进制串
    strcpy(hs,"00000400");
    sscanf(hs,"%x",&i);
    ltoa(i,b,2);
    sprintf(bs,"%032s",b);
    printf("hs=%s,bs=%s\n",hs,bs);
// ASC\GBK字符串转十六进制串
    strcpy(s,"a汉");
    i=0;
    while (1) {
        if (0==s[i]) break;
        sprintf(hs+i*2,"%02X",(unsigned char)s[i]);
        i++;
    }
    setlocale(LC_ALL,"chs");
    printf("s=%s,hs=%s\n",s,hs);
// 十六进制字符串转成汉字(GBK)及字符(ASC)
    strcpy(hs,"61BABA");
    i=0;
    while (1) {
        if (1!=sscanf(hs+i*2,"%2x",&v)) break;
        s[i]=(char)v;
        i++;
    }
    s[i]=0;
    printf("hs=%s,s=%s\n",hs,s);

    return 0;

}
//i=1024,bs=00000000000000000000010000000000
//i=1024,hs=00000400
//hs=00000400,i=1024
//bs=00000000000000000000010000000000,hs=00000400
//bs=00000000000000000000010000000000,i=1024
//hs=00000400,bs=00000000000000000000010000000000
//s=a汉,hs=61BABA
//hs=61BABA,s=a汉
赵4老师 2017-11-13
  • 打赏
  • 举报
回复
再供参考:
#include <stdio.h>
#include <stdlib.h>
char buf[17];
union U {
  unsigned short int aa;
  struct {
    unsigned int bb:7;//(bit 0-6)
    unsigned int cc:6;//(bit 7-12)
    unsigned int dd:3;//(bit 13-15)
  };
} u;
void main() {
                //bbbbbbbbbbbbbbbb
                //iiiiiiiiiiiiiiii
                //tttttttttttttttt
                //111111
                //5432109876543210
                //::::::::::::::::
    u.aa=0xE07F;//1110000001111111
    printf("bb==%d,cc==%d,dd==%d\n",u.bb,u.cc,u.dd);
    u.bb=0x41;
    u.cc=0x21;//dddccccccbbbbbbb
    u.dd=5;   //1011000011000001
    printf("aa==0x%04X==%016s(2)\n",u.aa,itoa(u.aa,buf,2));
}
//bb==127,cc==0,dd==7
//aa==0xB0C1==1011000011000001(2)

赵4老师 2017-11-13
  • 打赏
  • 举报
回复
理解讨论之前请先学会如何观察! 仅供参考:
#include <stdio.h>
#pragma pack(push,1)
union U {
    unsigned char byte;
    struct BF {
        unsigned int b0:1;//a
        unsigned int b1:1;//b
        unsigned int b2:1;//c
    } bf;
} u;
#pragma pack(pop)
unsigned char bt;
int a,b,c;
int main() {
    for (bt=0;bt<8;bt++) {
        u.byte=(unsigned char)bt;
        a=u.bf.b0;
        b=u.bf.b1;
        c=u.bf.b2;
        printf("byte 0x%02x -- c:%d b:%d a:%d\n",bt,c,b,a);
    }
    for (c=0;c<2;c++)
    for (b=0;b<2;b++)
    for (a=0;a<2;a++) {
        u.bf.b0=a;
        u.bf.b1=b;
        u.bf.b2=c;
        bt=u.byte;
        printf("c:%d b:%d a:%d -- byte 0x%02x\n",c,b,a,bt);
    }
    return 0;
}
//byte 0x00 -- c:0 b:0 a:0
//byte 0x01 -- c:0 b:0 a:1
//byte 0x02 -- c:0 b:1 a:0
//byte 0x03 -- c:0 b:1 a:1
//byte 0x04 -- c:1 b:0 a:0
//byte 0x05 -- c:1 b:0 a:1
//byte 0x06 -- c:1 b:1 a:0
//byte 0x07 -- c:1 b:1 a:1
//c:0 b:0 a:0 -- byte 0x00
//c:0 b:0 a:1 -- byte 0x01
//c:0 b:1 a:0 -- byte 0x02
//c:0 b:1 a:1 -- byte 0x03
//c:1 b:0 a:0 -- byte 0x04
//c:1 b:0 a:1 -- byte 0x05
//c:1 b:1 a:0 -- byte 0x06
//c:1 b:1 a:1 -- byte 0x07

CT8100 2017-11-13
  • 打赏
  • 举报
回复
引用 12 楼 zhao4zhong1 的回复:
“Talk is cheap, show me the code.”
typedef struct _Float
{
    int  mantissa:23;
    int exponent:8;
    int sign:1;
} Float;
typedef struct _Double
{

   long long   mantissa:9; //long long -> 64 bits
    int exponent:8;
    int sign:1;
} Double;
输出结果 4, 16
typedef struct _Float
{
    int  mantissa:24;
    int exponent:8;
    int sign:1;
} Float;
typedef struct _Double
{

   long long   mantissa:52; //long long -> 64 bits
    int exponent:8;
    int sign:1;
} Double;
输出结果 8 16 为啥前者的M有影响后者没有列
赵4老师 2017-11-13
  • 打赏
  • 举报
回复
“Talk is cheap, show me the code.”
CT8100 2017-11-13
  • 打赏
  • 举报
回复
引用 10 楼 hongwenjun 的回复:
这个是编译器的事,gcc和vc 对齐是不一样的
可是看了double输出的却是8字节,难道定义成Double结构体,vc编译器就采用了另一套规则么?求大神指教
hongwenjun 2017-11-13
  • 打赏
  • 举报
回复
这个是编译器的事,gcc和vc 对齐是不一样的
CT8100 2017-11-13
  • 打赏
  • 举报
回复
如果真是结构体内存直接给它最大地址,如long long 你有标签直接给你16字节,那int M也是可以给你到8字节呀,为什么这时候又会判断长度大于23就给你8字节,小于等于就给你4字节?
CT8100 2017-11-13
  • 打赏
  • 举报
回复
引用 6 楼 qq_20553613 的回复:
1、位域内存占用问题; 2、结构体内存对齐问题; 两者回炉重练。
哇大神讲清楚啊,,,当 M+E+S大于32时float的内存大小也会变成8字节,我现在疑问就是long long的处理,longlong只要存在符号位s的化内存大小就是16字节 难道只要long long 存在默认分配8字节,然后再看s标志位为1的话就是16字节?
CT8100 2017-11-13
  • 打赏
  • 举报
回复
引用 4 楼 ccssddnn218 的回复:
那这个怎么不是4+4+4=12呢?
typedef struct _Float
{
    int mantissa:23;
    int exponent:8;
    int sign:1;
} Float;
[quote=引用 3 楼 iloveyou418 的回复:]
typedef struct _Double
{
	long long mantissa:52; //long long -> 64 bits
	int exponent:11;
	int sign:1;
} Double;
大哥你是在搞事情啊,你上面有这个,长度当然16啊。 long long 8字节 int 4字节 8+4+4=16
[/quote] 我理解错了。,类型构成的三部分 sign +exponent+mantissa. 如果将上面long long mantissa改成long mantissa的话sizeof就会变成4字节,我想是不是,long起了4倍效果
Acuity. 2017-11-13
  • 打赏
  • 举报
回复
1、位域内存占用问题; 2、结构体内存对齐问题; 两者回炉重练。
赵4老师 2017-11-13
  • 打赏
  • 举报
回复
Really_want 2017-11-13
  • 打赏
  • 举报
回复
那这个怎么不是4+4+4=12呢?
typedef struct _Float
{
    int mantissa:23;
    int exponent:8;
    int sign:1;
} Float;
引用 3 楼 iloveyou418 的回复:
typedef struct _Double
{
	long long mantissa:52; //long long -> 64 bits
	int exponent:11;
	int sign:1;
} Double;
大哥你是在搞事情啊,你上面有这个,长度当然16啊。 long long 8字节 int 4字节 8+4+4=16
CT8100 2017-11-13
  • 打赏
  • 举报
回复
typedef struct _Double
{
	long long mantissa:52; //long long -> 64 bits
	int exponent:11;
	int sign:1;
} Double;
大哥你是在搞事情啊,你上面有这个,长度当然16啊。 long long 8字节 int 4字节 8+4+4=16
CT8100 2017-11-13
  • 打赏
  • 举报
回复
所以正常说,float是4字节,double是8字节,但是是不是编译器问题,设置的双倍精度字节情况列?
CT8100 2017-11-13
  • 打赏
  • 举报
回复
float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。 float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字; 搜索double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。
paschen 版主 2017-11-13
  • 打赏
  • 举报
回复
typedef struct _Double { long long mantissa:52; //long long -> 64 bits int exponent:11; int sign:1; } Double; 改成 typedef struct _Double { long long mantissa:52; //long long -> 64 bits long long exponent:11; long long sign:1; } Double;

64,636

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧