美文网首页
信息的表示和处理

信息的表示和处理

作者: 漫游之光 | 来源:发表于2018-09-13 20:59 被阅读0次

比特及位级运算

现代计算机存储和处理信息以二进制信号表示,一个二进制数称为位。大多数的计算机使用8位,或者字节,作为最小可寻址单位,而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。内存的每个字节都由一个唯一的数字来标识,称为它的地址,所有可能地址集合就称为虚拟地址空间。

因为计算机中只有0和1,对这些0和1的编码和解码的方式就显得尤为重要。这里要介绍3种重要的编码,字符的编码,整数的编码和浮点数的编码。在这之前,先看看MP2的头部编码方式。在MP2的标准中,有关于头部的定义:

header() {  
    syncword            12      bits    bslbf  
    ID                  1       bit     bslbf  
    layer               2       bits    bslbf  
    protection_bit      1       bit     bslbf  
    bitrate_index       4       bits    bslbf  
    sampling_frequency  2       bits    bslbf
    padding_bit         1       bit     bslbf  
    private_bit         1       bit     bslbf  
    mode                2       bits    bslbf  
    mode_extension      2       bits    bslbf  
    copyright           1       bit     bslbf  
    original/home       1       bit     bslbf  
    emphasis            2       bits    bslbf 
} 

可以看出,这是以比特位单位的,而不是以字节为单位的。如果要取出对应的数据,我们就需要位操作。C语言提供了以下的位操作:

符号 名称 运算规则
&(按位) 两个都为1,则为1,否则为0
| (按位) 两个都为0,则为0,否则为1
~(按位) 0变为1,1变为0
^(按位) 异或 两个相异为1,相同为0
&& 两个数都非0,则返回1,否则返回0
| | 两个数都为0,则返回0,否则返回1
! 如果非0,返回0,否则返回1
<< 左移 将数据的n比特左移,在右边补0
>> 右移 逻辑右移:将数据向右移动n比特,左边补0
算数右移:将数据向右移动n比特,左边补最高位

值得注意的是,移位的位数不能大于等于数据本身的位数,因为没有定义这种运算,取决于机器的实现。

通过位运算。我们可以取出对应的分量,但我们还需要每一个分量的解释,下面给出MP2(MP3的头部和MP2的格式是一样的,但是现在的MP3里面不仅有音频数据,还有艺术家名,封面等信息,解析要复杂一些,这里就以MP2为例)标准中关于sampling_frequency(采样率)的解释:

'00' 44.1 kHz  
'01' 48 kHz  
'10' 32 kHz  
'11' reserved 

下面的程序演示了如何从一个MP2文件中解析出一些简单的头部信息:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    unsigned char head[4];
    FILE *fp = fopen("test.mp2","rb");
    /* ignore the synchronization process */
    fread(head,1,4,fp);
    fclose(fp);
    int sampling_frequency = (head[2] >> 2) & 0x03;
    switch (sampling_frequency)
    {
        case 0:
            printf("sampling frequency = 44.1Khz\n");
            break;
        case 1:
            printf("sampling frequency = 48Khz\n");
            break;
        case 2:
            printf("sampling frequency = 32Khz\n");
            break;
        case 3:
            printf("reserved\n");
            break;
        default:
            break;
    }
    return 0;
}

对于其他数据的解析,也是类似的,就是先应用比特运算,取出对应的成员,然后根据协议,去解释各个成员。

字符的表示

对字符的表示,采取了一一映射的方式。通过查看ASCII码表我们可以知道,每一个字符都被映射到了一个整数。这里值得注意的是,对于字符串的表示,需要有一个特别的字符(‘\0’),来表示字符串的结束,不然无法确定字符串的结束。其他的编码方案,采取的策略也是差不多的,值得注意的是有的编码不是定长的,有的字符是1个字节,有的是2个字节,通常是为了和ASCII码兼容。汉字的编码需要两个字节,我们有时候会遇到这种情况,编辑器中删除一个汉字需要按两次删除,并且第一次删除后会出现一个乱码,这就是因为编码的方式不同导致的。

整数的表示和计算

先说说无符号数的表示,对向量\vec{x} =[x_{w-1} , x_{w-2} , ... , x_0]:
B2U_w(\vec{x}) = \sum^{w-1}_{i=0}x_i2^i
函数B2U_w (Binary to Unsigned的缩写,长度为w)来表示。最小值:0(64位的unsigned类型最小值为0x00000000),最大值:2^{w−1}(64位的unsigned类型最大值为0xFFFFFFFF)。

对于无符号数的加法和乘法,个人认为只需要记住一点,就是无符号数的位数有限,对于超出的部分,直接丢弃。这个结果也可以用另一种表述方式,即结果mod 2^ww是位数。

对于无符号数的减法,有一个比较有意思的结论,我们知道如果无符号数的最大值2^{w−1} + 1,那么会溢出,得到的结果将是0,于是有2^{w−1} + 1 = 0;于是有1 = -2^{w−1},也就是说,1是2^{w−1}的逆元。这样,无符号数的减法可以用加上对应的逆元实现。下面上一个具体的例子:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    printf("0xFFFFFFFF + 0x1 = %u.\n",0xFFFFFFFFu + 0x1u);
    int i;
    for(i=0;i<10;i++){
        unsigned int n1 = rand();
        unsigned int n2 = rand();
        unsigned int neg = ~n2 + 1; /* n2 + ~n2 = 0xFFFFFFFF, so n2 + ~n2 + 1 = 0 */
        printf("%u %u\n",n1 - n2, n1 + neg);  /* equal */
    }
    return 0;
}

有符号数的表示,对向量\vec{x}=[x_{w−1} , x_{w−2} , ... , x_0 ]:
B2T_w (\vec{x})=−x_{w−1} 2^{w−1}+\sum^{w−2}_{t=0}x_i 2^i
函数B2T_w(Binary to Two's-complement的缩写,长度为w)来表示。最小值:-2^{w-1}(64位的int类型最小值为0x80000000),最大值:2^{w−1}−1(64位的int类型最大值为0x7FFFFFFF)。

从无符号数的减法我们可以看出,在溢出的情况下,每个数都有逆元。现在我们将这些逆元直接就表示为负数,这样会使得正数减少一部分。通过观察可以发现,补码和无符号数,如果表示相同,那么相加之和的表示也是相同的。所以,加法的法则是一样的,这一点书上有论证,这里就说明结论就可以了,乘法也有类似的结论。对于加法和乘法,有符号数如果有溢出,仍然需要截断,也就是mod2^w。下面是一个具体的例子:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int x = 0x7FFFFFFF * 2;
    unsigned int y = 0x7FFFFFFFu * 2u; 
    printf("0x7FFFFFFF * 2 = %d, 0x7FFFFFFFu * 2u = %x.\n",x,y); /* 0xFFFFFFFE = -2 */
    printf("0x7FFFFFFF + 0x1 = %d.\n",0x7FFFFFFF + 0x1); /* positive overflow */
    printf("0x80000000 + 0x80000000 = %d.\n",0x80000000 + 0x80000000);/* negtive overflow */
    return 0;
}

无符号整数和有符号整数的转化

对于大多数C语言的实现,处理同样的字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,但是位模式不变。但是还有一点是重要的,就是当执行一个运算的时候,如果一个数是有符号的,而另一个数是无符号的,C语言会隐式地将有符号转化为无符号,这会导致一些细微的错误。

#include <stdio.h>
#include <string.h>

int strlonger(char *s, char *t){
    return strlen(s) - strlen(t) > 0; /*  strlen()return unsigned, if t is longer than s, this will return true*/
}

int main(int argc, char const *argv[])
{
    if(-1 < 0U){
        printf("-1 < 0U\n");
    }else{
        printf("-1 >= 0U\n"); /* this will execute */
    }
    return 0;
}

寻址和字节顺序

在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。这里,就有两种方式进行排序了,可以把最低有效字节放在低位,称为小端法;也可以把最高的有效字节放在前面,称为大端法。其实这两种差异可以看成是数据协议的不同,字节序的转化主要用在了网络编程。因为网络是大端序,而主机可能是大端,也有可能是小端。如果不统一字节序,那么路由器这些网络设备就不能正确的读取数据(如目的IP地址,端口号等)。下面这个程序显示了网络字节序和主机字节序。

#include<stdio.h>
#include <arpa/inet.h>

int main(){
    unsigned int x = 0x12345678;
    unsigned char *p =(unsigned char *)&x;
    printf("%x %x %x %x\n",p[0],p[1],p[2],p[3]);
    unsigned int y = htonl(x);
    p = (unsigned char *)&y;
    printf("%x %x %x %x\n",p[0],p[1],p[2],p[3]);
    return 0;
}

在x86平台下运行结果如下:

78 56 34 12
12 34 56 78

IEEE浮点表示

IEEE浮点标准用V=(−1)^s \times M \times 2^E的形式来表示一个数:

  • 符号(sign)s决定这个数是负数(s=1)还是正数(s=0),而对于数值0的符号位解释作为特殊情况处理。
  • 尾数(significand)M是一个二进制小数,它的范围1 -2,或者是0-1
  • 阶码(exponent)E的作用对浮点数加权,这个权重是2的E次幂。
  • 一个单独的符号s直接编码s,k位的阶码字段exp编码阶码E,n位小数字段frac编码尾数M,顺序是s,exp,frac。
  • 64位机器上,单精度s,exp,frac字段分别为1位,8位,和23位;双精度s,exp,frac字段分别为1位,11位,52位。

对于一个具体的浮点数,要得到它的具体值,还要对M和E的编码进行解析,有以下的三种情况:

  • 规格化的值:exp不全为0,也不全为1。
    E的阶码的值E=exp-Bias,M=1+f,其中Bias等于2^{k-1}−1
  • 非规格化的值:exp全为0.
    E=1-Bias,M=f,其中Bias等于2^{k-1}−1
  • 特殊值:exp全为1.
    当frac=0,s=0时,表示+\infty;当frac=0,s=1时,表示-\infty。当frac非0时,表示不是一个数。

其实从这里可以看出,这个格式的解析仍然是取出对应的成分,然后进行解释。下面是一个例子:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    unsigned int x;
    unsigned int y;
    unsigned int z;   

    float f1 = 123.456f, f2 =  123.456f;
    unsigned int *neg_f = (unsigned int *)&f2;
    *neg_f = *neg_f ^ 0x80000000;
    printf("f = %f,neg_f = %f.\n",f1,*((float *)neg_f));

    x = 0x00000000;
    y = 0x80000000;
    float *positive_zero = &x;
    float *negtive_zero = &y;
    printf("positive zero = %f, negtive zero = %f.\n",positive_zero,negtive_zero);

    x = 0x7F800000;
    y = 0xFF800000;
    z = 0x7F800001;
    float *positive_infinity = (float *)&x;
    float *negtive_infinity = (float *)&y;
    float *nan = (float *)&z;
    printf("positive infinity = %f,negtive infinity  = %f, nan = %f.\n",*positive_infinity,*negtive_infinity,*nan);
    return 0;
}

输出为:

f = 123.456001,neg_f = -123.456001.
positive zero = 0.000000, negtive zero = 0.000000.
positive infinity = 1.#INF00,negtive infinity  = -1.#INF00, nan = 1.#QNAN0.

这个程序展示通过符号位来对一个小数取反,0的两种表示方式,以及特殊值的表示方式。

相关文章

  • 信息的表示和处理

    比特及位级运算 现代计算机存储和处理信息以二进制信号表示,一个二进制数称为位。大多数的计算机使用8位,或者字节,作...

  • 信息的表示和处理

    主要研究三种数字表示1、无符号编码2、补码编码3、浮点数编码 一些基本概念 整数表示相对小的数值范围,但是一个精确...

  • 信息的表示和处理

    在阅读《深入理解计算机系统》的过程之中,有一些知识点是我觉得有必要记录下来的,在这里进行一定的总结。 文本数据比二...

  • 信息的表示和处理

    信息的存储 字数据大小 计算机中,字长指的是指针数据标称大小,虚拟地址以字来进行编码的,所以字长w位的机器,可以表...

  • 信息的表示和处理

    现代计算机存储和处理的信息以二值信号表示。这些微不足道的二进制数字,或者称为位 (bit), 形成了数字革命的基础...

  • 信息的表示和处理(1):信息存储

    无符号编码:基于传统的二进制表示法,表示大于或等于0的数字 补码:有符号整数的常见方式(可正可负) 浮点数:表示实...

  • 信息的表示和处理(2):整数表示

    精确定义如何编码和操作整数的数学术语: 1.1 整数数据类型 唯一一个与机器相关的类型是long,其他类型的取值范...

  • Charpter Two 信息的表示和处理

    2.1 信息存储2.1.4 表示字符串独立性(文本数据/二进制数据)文本数据比二进制数据具有更强的平台独立性原因:...

  • 1. 信息的表示和处理

    深入理解计算机系统,是从计算机的底层往上看,从下到上一层一层的分析。 在计算机中,使用二进制来表示最基本的单位,原...

  • 信息的处理与表示

    计算机以8位为一块(称作字节byte)作为最小的可寻址的内存单位。虚拟地址空间(virtual address s...

网友评论

      本文标题:信息的表示和处理

      本文链接:https://www.haomeiwen.com/subject/ulymgftx.html