C语言算术随笔
C语言应用中,有一些需要留意的小事项,随笔做个算术方面的memo。
基本的算术计算方式
对于CPU, MCU, DSP里,都有基本的ALU( Arithmetic Logic Unit) 算术逻辑单元,最常见的是32的ALU,由2个32位的ALU联合可以进行64位的计算。
在如嵌入式系统里,有各种位长的变量,如8位(byte), 16位(4 bytes)以及32位(8 bytes)等等,有整数,长整, 浮点等的类型。主要涉及到存放的格式和占用存储空间不同。
当一个变量或常数被取出用于计算过程,除了指定或涉及了64位长整或双精度浮点,一般就是作为32位数据加入运算。譬如定义为uint8_t d1和uint16_t d2的变量,在相加运算时,d1+d2的结果是32位数据,当赋值给不同类型的变量时,也即涉及到最后的存储时,会进行结果根据类型的格式化。如定义了uint32_t d3及计算d3=d1+d2,则d1+d2的结果不进行处理赋值给d3,如果定义了uint8_t d3及计算d3=d1+d2,则d1+d2的结果只保留最低字节赋值给d3。需要注意计算过程是按照有符号的计算方式进行。 譬如全正数的减法运算也可能产生负值。所以也要基于结果的可能性,安排最后的存储变量的格式(有符号变量或无符号变量),否则会产生异常。计算结果是正值直接存进变量的存储空间,计算结果是负值转换成补码存进变量的存储空间。实际上正数的补码是其本身,因此计算结果存储的都是补码,计算结果赋值给变量,就是把补码存进变量的存储空间的过程,这个存入过程只做格式化位数处理,不做正负识别处理(可以把一个负值的补码存入无符号数变量空间)。而在补码数据从变量存储空间取出来后,会根据变量类型进行识别使用。
如果要在计算过程中进行特定阶段数据的规格化,则采用强制转换方式,也即在阶段数据前用 (类型) 进行指定。如uint8_t d1和uint16_t d2的计算,结果放到uint16_t d3,d3 = d1 + (uint8_t)d2的结果就是d2的低字节和d1的相加结果,通过(uint8_t)d2在计算过程中清零了d2的高字节。
无符号数递减(向下计数)的一个细节
在进行循环控制时,采用无符号数递增的方式是没有问题的,如: for(uint16_t i=0; i<1000; i++) ; 1000次循环后就退出继续后面的代码执行。
采用无符号数递减(向下计数)到0则有一个细节需要注意,如: for(uint16_t i=1000 ;i>0; i–) ; 也,是没有问题的,变量i等于0时退出循环,继续后面的代码执行。
但 for(uint16_t i=1000 ;i>=0; i–) ; 是有问题的,会进入异常无限循环,因此在i=0时还会再减一次,得到的值是负的最大值,而这个负的最大值按照补码存进变量i后,因为i定义为无符号数,所以变量i再次被取出来用时,最高位的1不被识别位符号位,而是一个数值位,从而i是个正值,从而i>=0的条件满足,于是循环就无限循环了。
整数的求余
求余数是整数的操作,对于浮点数是没有求余的概念。整数分为正整数和负整数,0就不考虑了。正整数的求余好理解属于小学数学范畴,而负数的求余数其实是有些特点,譬如-10%6是等于4么?确实不是,看这篇 负数求余的补码分析 .
负数的输入
上面都提到了补码,对于编程者而言,在进行负数的输入时,涉及到十进制和非十进制的输入差别。 譬如定义了int i;的变量, 对于十进制的输入,用负号-表示负数,如i=-1, 系统会以明码进行识别,通过printf(“data: %d \r\n”, i);打印结果:
对于16进制的输入,用最高位为1表示负数,如i=0x80000001,系统会以补码进行识别,从而不是识别为明码-1,printf(“data: %d \r\n”, i);打印结果: 需要注意的一点是,十六进制常数,如果位数为32位,最高位为1,会被识别为负数,而如果一个十六进制常数,最高位为1,但不在32位的最高位上,则还是识别为正数。 如i=0x8001识别为正数,因为对应的32位扩展为0x00008001, 最高位并不为1。
–TBC–
|