时间变成float_计算机中int和float的储存结构以及运算性能

一、前言

在前不久的转正面谈中,提到一个问题:计算机中 int 和 float 的运行到底那个更快?按想当然的理解,当然是int更快,但是我自己也没真正去测试过,不是很清楚,在实际测试后发现结果与想象大不相同。因此,本文记录一下对这个问题的思考与实践。

二、int和float的储存结构

要解答这个问题,首先需要知道 int 和 float 的储存结构。int 和 float 都是4个字节,但存储结构不一样,并且表示范围也不一样。紧接着就出现了一个疑问,到底哪些 int 值是 float 不能表示的呢?

2.1 小数十进制与二进制的转换

小数由二进制转换成十进制的原理跟整数一样,例如二进制11.11转换为十进制如下:

1 * 2^1 + 1 * 2^0 + 1 * 2^(-1) + 1 * 2^(-2) = 3.75

十进制转换成二进制:整数部分就不说了,小数部分采用乘2取整的方式,比如3.75的小数部分为0.75,步骤如下:

  1. 先用 0.75 乘 2 等于 1.5,取整数部分 1;
  2. 再用 1.5 的小数部分 0.5 乘 2 等于 1 ,此时已经没有小数部分了,因此 0.75 对应的二进制为 0.11。

根据以上内容,3.75 对应的二进制为 11.11。注意这里的 3.75 和 11.11 只是浮点数十进制和二进制的不同表现形式,储存结构是一样的,因为本来就是一个数,那么在内存中是如何储存的呢?详见下文。

2.2 float的存储结构

float 在内存中占32位,第一位是符号位(sign),符号位后面 8 位是指数位(exponent),最后 23 位是尾数(mantissa)。 float值的二进制表示形式如下(该表达式对应上述二进制存储结构):

sign * mantissa * 2 ^ (exponent)
  • 符号位:表示浮点数的正负,0为正,1为负;
  • 指数位:实际上也有正负,但没有单独的符号位,计算机中使用二进制,指数表示的也是 2 的 N 次幂,8 位指数表达的范围是 0至255,而实际上是-127至128,也就是说,实际的指数等于指数位表示的数值减127。
  • 尾数位:只表示二进制的小数点后的部分,小数点前的那位被省略了,当指数位全部为0时,省略的是0,否则省略的是1,下面看个例子:

二进制 11.11 表示成指数形式是 1.111 * 2 ^ 1,0.1111 表示成指数形式是 1.111 * 2 ^ (-1)。由此可见,正常情况下二进制的指数形式是肯定有一个 1 的,所以存储的时候直接省略。但是在指数位全部为0时,指数是-127,这个数字是有特殊含义的,在尾数全部为0时代表的数值是0,省略的那位是0,如果省略的是 1 那么0这个数字就没法用float表示了。

2.3 float无法表示的int

了解上文内容后,再来看一下 3.75 的储存结构。首先转换为二进制为 11.11,然后转换为二进制的指数形式为 1.111 * 2 ^ 1。由此可以得知尾数部分是 111(将小数点前的1省略了),不足 23 的后边补 0,指数位是 1 + 127 = 128,对应二进制10000000,所以 3.75 的储存结构如下:

01000000011100000000000000000000

反过来转换一下,符号位是 0,为正数,指数位是128,实际指数是 128 - 127 = 1,尾数是111,再加上省略的那位就是 1.111,所以对应的二进制指数形式为 1.111 * 2 ^ 1,转换为十进制是 3.75。

到这里可以看出,实际上尾数位决定了浮点数的精度,尾数只有23位,加上省略的那位就是24位,如果一个 int 类型的值小于等于 2 ^ 24,那么 float 是完全可以表示的,否则就不一定了。

假如一个 int 数值的二进制表示形式是100000000000000000000000,表示成指数形式是1.00000000000000000000000 * 2 ^ 23,对应的 float 的类型,尾数位全部为 0,指数位是 23 + 127 = 150,这样完全没有问题。

假如一个 int 数值的二进制表示形式是1000000000000000000000001,表示成指数形式是1.000000000000000000000001 * 2 ^ 24,对应的float的类型尾数位是000000000000000000000001一共24位,这样就完全超出了 float 最多容纳 23 位尾数的能力。所以就不能正确表达这个 int 值了。

由此也可以得出不能被 float 准确表达的最小 int 值是2 ^ 24 + 1。我们再将1000000000000000000000001的值加1,变成1000000000000000000000010,这样变换为指数形式可以看出尾数又变为了23位,也就是说25位的二进制整数最后一位是0才能被float准确表示,每2个数就有一个不能被准确表示。如果是26位的二进制整数最后两位都是0才可以被float准确表达,每4个数就有3个不能被准确表示,以此类推。

三、int 和 float 的运算性能

了解完 int 和 float 的储存结构区别后,我们再来测试一下二者的运算性能,此处主要测试二者做乘除法运算的性能,代码如下,需要注意的是此处单纯的使用主线程进行运算,查看运算时间的差异(各做100000000000次运算)。

注意:不使用线程是因为线程有资源优先级的问题,要排除这个影响。同时,不使用数值类型强转,避免其他性能消耗影响 。最后,为了普适化,使用 10 和 3 这种除不尽的数值。
#include <iostream>
#include <Windows.h>
#include <string.h>

using namespace std;

#define COUNT 100000000000;
static DWORD crtTime = 0;

void PrintCrtTime()
{
    crtTime = GetTickCount();
    cout << endl;
    cout << "当前时间:" << crtTime << endl;
}

void PrintElapseTime(string method)
{
    DWORD crt = GetTickCount();
    cout << "执行方法:" << method.c_str() << " 当前时间:" << crt << endl;
    cout << "执行方法:" << method.c_str() << " 使用时间:" << crt - crtTime << endl;
}

void IntMultiplyInt()
{
    PrintCrtTime();
    int count = COUNT;
    int a = 10;
    int b = 3;
    int c;
    while (count > 0)
    {
        a * b;
        //c = (int)(a * b);
        count--;
    }
    PrintElapseTime("int乘int");
}

void IntMultiplyFloat()
{
    PrintCrtTime();
    int count = COUNT;
    int a = 10;
    float b = 3.0;
    int c;
    while (count > 0)
    {
        a * b;
        //c = (int)(a * b);
        count--;
    }
    PrintElapseTime("int乘float");
}

void FloatMultiplyFloat()
{
    PrintCrtTime();
    int count = COUNT;
    float a = 10.0;
    float b = 3.0;
    int c;
    while (count > 0)
    {
        a * b;
        //c = (int)(a * b);
        count--;
    }
    PrintElapseTime("float乘float");
}

void IntDivisionInt()
{
    PrintCrtTime();
    int count = COUNT;
    int a = 10;
    int b = 3;
    int c;
    while (count > 0)
    {
        a / b;
        //c = (int)(a / b);
        count--;
    }
    PrintElapseTime("int除int");
}

void IntDivisionFloat()
{
    PrintCrtTime();
    int count = COUNT;
    int a = 10;
    float b = 3.0;
    int c;
    while (count > 0)
    {
        a / b;
        //c = (int)(a / b);
        count--;
    }
    PrintElapseTime("int除float");
}

void FloatDivisionFloat()
{
    PrintCrtTime();
    int count = COUNT;
    float a = 10.0;
    float b = 3.0;
    int c;
    while (count > 0)
    {
        a / b;
        //c = (int)(a / b);
        count--;
    }
    PrintElapseTime("float除float");
}

int main()
{
    IntMultiplyInt();
    IntMultiplyFloat();
    FloatMultiplyFloat();

    IntDivisionInt();
    IntDivisionFloat();
    FloatDivisionFloat();

    return 0;
}

运算结果如下:

208497f7b8bdfb8de260300ed9ad85ec.png

可以看出,int 乘 int 消耗时间最多,float数据做运行的速度甚至比 int 要快那么一点点,但是从整体看来,区别不大,所以在100000000000次运算下,可以说单纯的乘除运算,int 和 float 的性能几乎一样。

但是,通常情况下,使用代码进行数值运算后,肯定要储存结果,那么就存在数据转换问题,接下来我们使用 int c 储存结果,看看运算的性能差别,修改代码:

int c; c = (int)(a * b); c = (int)(a / b);

运算结果如下:

e36c2aacff3193448d81a19f76a822cf.png

可以看得出来以下三点:

  1. int 除 int 消耗性能最高;
  2. 乘除法运算中,不同类型相乘除相对比较耗时;
  3. 都使用float运算,耗时最少,性能最高;

那如果把 c 改成 float 呢?修改代码:

float c; c = (float)(a * b); c = (float)(a / b);

运行结果如下:

1be89d8f3f4d623c0cf3ce4e36d6e53d.png

可以看出,不同类型运算依旧相对比较耗时,同时,float 类型做乘除运算都比 int 要快一些,特别是除法运算,性能更高。

四、总结

经过以上测试,在平时写代码的时候,想提高代码运行的效率,我总结了以下三点:

  1. 尽量使用同类型进行运算,这样相对比较快,节省了类型转化的时间。
  2. 不用特别在意 float 的乘除问题,float单纯做乘除法的运行性能差距不大,不需要特意将除法改写为乘法,反而特意改写后可能会造成多次复合运算后的精度不准确。当然,实际情况可以测试一下来判断要不要改写。
  3. float 的乘除运算速度比 int 要快,很多人潜意识都觉得 float 的使用更消耗性能,好多时候都会强转成 int,没必要,不仅运算更慢,还不精确。当然,也不是说用 float 就是最好的选择,还需要考虑到 float 无法准确表示某些 int 数值的情况。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值