C++学习------cmath头文件的源码学习02

209 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情

续接上文,cmath头文件的源码学习01

宏函数定义---分类函数

isfinite---返回输入数x是否有限值

我们来看几个例子,其它三个数都是无限或无法解释的实数:

  printf ("isfinite(0.0)       : %d\n",isfinite(0.0));
  printf ("isfinite(1.0/0.0)   : %d\n",isfinite(1.0/0.0));
  printf ("isfinite(-1.0/0.0)  : %d\n",isfinite(-1.0/0.0));
  printf ("isfinite(sqrt(-1.0)): %d\n",isfinite(sqrt(-1.0)));
  //测试结果:
isfinite(0.0)       : 1
isfinite(1.0/0.0)   : 0
isfinite(-1.0/0.0)  : 0
isfinite(sqrt(-1.0)): 0

我们来看看它的具体实现

//glibc/sysdeps/ieee754/flt-32/s_finitef.c
 29 #ifndef FINITEF
 30 # define FINITEF __finitef
 31 #endif
 32 
 33 int FINITEF(float x)
 34 {
 35     int32_t ix;
 36     GET_FLOAT_WORD(ix,x);
 37     return (int)((uint32_t)((ix&0x7f800000)-0x7f800000)>>31);
 38 }

实现方式与ieee754对浮点数的解释一致,首先转换获取该浮点数的位解释(第31位符号位,30到23位为指数域,22到0位为小数域);

(ix&0x7f800000)是截取其指数域,然后减去0x7f800000得到两者的差,然后左移31位,相当于此时只保留第32位的值。因为指数域值为0-255,其中0-254做减法减去255之后都是不够的,会导致最高位变为1,这时所表示的浮点数也正式有限的,而指数域全为1就会导致255-255,最高位为0,最后return也是0,此时表示的数也是无穷大的

isinf---返回输入数x是否是无限值

还是上面的例子:

  printf ("isinf(0.0)       : %d\n",isinf(0.0));
  printf ("isinf(1.0/0.0)   : %d\n",isinf(1.0/0.0));
  printf ("isinf(-1.0/0.0)  : %d\n",isinf(-1.0/0.0));
  printf ("isinf(sqrt(-1.0)): %d\n",isinf(sqrt(-1.0)));
  //测试结果
isinf(0.0)      : 0
isinf(1.0/0.0)  : 1
isinf(-1.0/0.0) : 1
isinf(sqrt(-1.0): 0

这一次两个除0的情况都是无限值,函数逻辑如下:

//glibc/sysdeps/ieee754/flt-32/s_isinff.c
 17 int
 18 __isinff (float x)
 19 {
 20     int32_t ix,t;
 21     GET_FLOAT_WORD(ix,x);
 22     t = ix & 0x7fffffff;
 23     t ^= 0x7f800000;
 24     t |= -t;
 25     return ~(t >> 31) & (ix >> 30);
 26 }

判断逻辑与isfinite类似,t = ix & 0x7fffffff是获取指数域+小数域;

t ^= 0x7f800000按位异或,对指数域,只有为0的位置异或之后为1,为1的位置异或之后为0,小数域为0的位置异或之后为0,为1的位置异或之后为1,相当于不改变小数域,指数域按位取反;

t |= -t按位或上-t,注意到这里使用的是int32_t,所以转换为负数之后使用补码表示,即负数的补码等于原来正数的反码+1,这样看来-t,就是对原来的t,符号域为1(因为t的符号域截断为0),指数域再按位取反,即变为最开始的指数域,小数域按位取反,然后再加1;

这里我们扩展思考一下: 二进制下的数字都可以写成(A)1(B)的形式,其中A表示一串01字符串,1表示从右向左的出现的第一个数字1,B表示空(奇数)或者是连续的0(偶数),即:

  • 偶数:(A)1(00…0)
  • 奇数:(A)1 那么,-t的运算是,所有位置取反+1,即变形如下(Ā表示所有位置取反):
  • 偶数:(Ā)0(11…1) + 1 = (Ā)1(00…0)
  • 奇数:(Ā)0 + 1 = (Ā)1 那么,t|(-t),就是
  • 偶数:(Ā)1(00…0) | (A)1(00…0) = (11…1)1(00…0)
  • 奇数:(Ā)1 | (A)1 = (1…1)1 所以最后t要么全为1,要么一堆1跟着一堆0; 这里我们考虑无限函数判断的三种情况:负无限,正无限,0,分别如下:
数据负无限正无限0
ix(1)(11111111)(000...0)(0)(11111111)(000...0)(0)(00000000)(000...0)
t = ix & 0x7fffffff,截断指数域和小数域(0)(11111111)(000...0)(0)(11111111)(000...0)(0)(00000000)(000...0)
t ^= 0x7f800000,指数域取反,小数域不变(0)(00000000)(000...0)(0)(00000000)(000...0)(0)(11111111)(000...0)
t|=-t,即t|(t的反码+1)(0)(00000000)(000...0)|(全1+1)(0)(00000000)(000...0)|(全1+1)(0)(11111111)(000...0)|((1)(0..0)(1)+1)
结果溢出之后全为0溢出之后全为0全为1,小数域全为0
~(t >> 31),t是有符号数,最高位用符号位填充1...111...110...00
(ix >> 30)1...110...010...00
~(t >> 31) & (ix >> 30)1...110...010...00

这样来看,中间位运算的作用就是将无限数的特征(指数域全为1,小数域全为0)提取出来,然后利用原数据的符号位和指数域最高位将数据转换为正负1标识正无限和负无限,同时也可以将0表示为0,这也是符合该函数的返回值定义的,如果返回来其它值,那说明并不满足上面的特征,那就不是无限数。