0
点赞
收藏
分享

微信扫一扫

C++学习------cmath头文件的源码学习01---类型别名与宏函数

Raow1 2022-11-23 阅读 47

引言

cmath是C++对math.h头文件的封装,里面定义了一系列的数学函数,用来进行通用的数学计算和转换。我们来看看他的源码实现。

math.h

参考代码: ​​www.aospxref.com/android-12.…​​

类型别名

这里将double和float类型定义了别名,方便后续的使用。

25  typedef double __double_t;
26 typedef __double_t double_t;
27 typedef float __float_t;
28 typedef __float_t float_t;

宏常量定义

通过builtin方法获取最值的情况,定义边界值,包括INFINITY和NAN

30  #define HUGE_VAL __builtin_huge_val()
31 #define HUGE_VALF __builtin_huge_valf()
32 #define HUGE_VALL __builtin_huge_vall()
33
34 #define INFINITY __builtin_inff()
35
36 #define NAN __builtin_nanf("")

通过直接定义的方式,定义一些浮点数运算的标志值,后续计算函数当中会使用到。

38  #define FP_INFINITE 0x01
39 #define FP_NAN 0x02
40 #define FP_NORMAL 0x04
41 #define FP_SUBNORMAL 0x08
42 #define FP_ZERO 0x10
43
44 #if defined(__FP_FAST_FMA)
45 #define FP_FAST_FMA 1
46 #endif
47 #if defined(__FP_FAST_FMAF)
48 #define FP_FAST_FMAF 1
49 #endif
50 #if defined(__FP_FAST_FMAL)
51 #define FP_FAST_FMAL 1
52 #endif
53
54 #define FP_ILOGB0 (-INT_MAX)
55 #define FP_ILOGBNAN INT_MAX
56
57 #define MATH_ERRNO 1
58 #define MATH_ERREXCEPT 2
59 #define math_errhandling MATH_ERREXCEPT

POSIX扩展的常量,包括常用的自然常数,圆周率,对数等,便于后面的快速计算。

337  #define M_E                2.7182818284590452354        /* e */
338 #define M_LOG2E 1.4426950408889634074 /* log 2e */
339 #define M_LOG10E 0.43429448190325182765 /* log 10e */
340 #define M_LN2 0.69314718055994530942 /* log e2 */
341 #define M_LN10 2.30258509299404568402 /* log e10 */
342 #define M_PI 3.14159265358979323846 /* pi */
343 #define M_PI_2 1.57079632679489661923 /* pi/2 */
344 #define M_PI_4 0.78539816339744830962 /* pi/4 */
345 #define M_1_PI 0.31830988618379067154 /* 1/pi */
346 #define M_2_PI 0.63661977236758134308 /* 2/pi */
347 #define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */
348 #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
349 #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
350
351 #define MAXFLOAT ((float)3.40282346638528860e+38)

同样的,GNU的扩展中也有类似的值

390  /* GNU extensions. */
391
392 #if defined(__USE_GNU)
393 #define M_El 2.718281828459045235360287471352662498L /* e */
394 #define M_LOG2El 1.442695040888963407359924681001892137L /* log 2e */
395 #define M_LOG10El 0.434294481903251827651128918916605082L /* log 10e */
396 #define M_LN2l 0.693147180559945309417232121458176568L /* log e2 */
397 #define M_LN10l 2.302585092994045684017991454684364208L /* log e10 */
398 #define M_PIl 3.141592653589793238462643383279502884L /* pi */
399 #define M_PI_2l 1.570796326794896619231321691639751442L /* pi/2 */
400 #define M_PI_4l 0.785398163397448309615660845819875721L /* pi/4 */
401 #define M_1_PIl 0.318309886183790671537767526745028724L /* 1/pi */
402 #define M_2_PIl 0.636619772367581343075535053490057448L /* 2/pi */
403 #define M_2_SQRTPIl 1.128379167095512573896158903121545172L /* 2/sqrt(pi) */
404 #define M_SQRT2l 1.414213562373095048801688724209698079L /* sqrt(2) */
405 #define M_SQRT1_2l 0.707106781186547524400844362104849039L /* 1/sqrt(2) */
406 #endif

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

61  #define fpclassify(x) __builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, FP_ZERO, x)
62
63 #define isfinite(x) __builtin_isfinite(x)
64
65 #define isinf(x) __builtin_isinf(x)
66
67 #define isnan(x) __builtin_isnan(x)
68
69 #define isnormal(x) __builtin_isnormal(x)
70
71 #define signbit(x) \
72 ((sizeof(x) == sizeof(float)) ? __builtin_signbitf(x) \
73 : (sizeof(x) == sizeof(double)) ? __builtin_signbit(x) \
74 : __builtin_signbitl(x))

fpclassify:返回输入数x的类型

  • FP_INFINITE:Positive or negative infinity (overflow)
  • FP_NAN:Not-A-Number
  • FP_ZERO:Value of zero
  • FP_SUBNORMAL:Sub-normal value (underflow)
  • FP_NORMAL:Normal value (none of the above) 这里我们很难找到通用的代码逻辑,因为这个操作是与具体的平台相关的,我们参考glibc库中的实现来看看这个函数的实现: (glibc的源代码在这里可以下载:www.gnu.org/software/li…)

957 /* Return number of classification appropriate for X.  */958 # if ((__GNUC_PREREQ (4,4) && !defined __SUPPORT_SNAN__)              \
959 || __glibc_clang_prereq (2,8)) \
960 && (!defined __OPTIMIZE_SIZE__ || defined __cplusplus)961 /* The check for __cplusplus allows the use of the builtin, even
962 when optimization for size is on. This is provided for
963 libstdc++, only to let its configure test work when it is built
964 with -Os. No further use of this definition of fpclassify is
965 expected in C++ mode, since libstdc++ provides its own version
966 of fpclassify in cmath (which undefines fpclassify). */967 # define fpclassify(x) __builtin_fpclassify (FP_NAN, FP_INFINITE, \
968 FP_NORMAL, FP_SUBNORMAL, FP_ZERO, x)969 # else970 # define fpclassify(x) __MATH_TG ((x), __fpclassify, (x))971 # endif

同样的,我们来看看第二个实现,可以看到,这里使用宏连接的方式,判断参数的类型然后确定调用__fpclassifyf函数,还是__fpclassifyl函数(这种宏的拼接手法在glibc代码中很常见,同时这也导致了代码跳转非常多,变得晦涩难懂)

922 # define __MATH_TG(TG_ARG, FUNC, ARGS)      \
923 (sizeof (TG_ARG) == sizeof (float) \
924 ? FUNC ## f ARGS \
925 : sizeof (TG_ARG) == sizeof (double) \
926 ? FUNC ARGS \
927 : FUNC ## l ARGS)928 #endif

继续往下追对应的函数__fpclassifyf,对应该函数,有两种实现,ieee754和riscv,这是两种不同的标准,我们优先看最为通用的ieee754

C++学习------cmath头文件的源码学习01---类型别名与宏函数_cmath

//glibc/sysdeps/ieee754/flt-32/s_fpclassifyf.c24 int25 __fpclassifyf (float x)
26 {
27 uint32_t wx;
28 int retval = FP_NORMAL;
29
30 GET_FLOAT_WORD (wx, x);
31 wx &= 0x7fffffff;
32 if (wx == 0)
33 retval = FP_ZERO;
34 else if (wx < 0x800000)
35 retval = FP_SUBNORMAL;
36 else if (wx >= 0x7f800000)
37 retval = wx > 0x7f800000 ? FP_NAN : FP_INFINITE;
38
39 return retval;
40 }

这下一看,逻辑就比较清晰了,首先默认是FP_NORMAL,然后调用GET_FLOAT_WORD宏,作用如下: 定义了一个union结构体,用于将float数转换为uint32的字解释,一个非常精妙的方法。

//glibc/include/math.h
63 typedef union
64 {
65 float value;
66 uint32_t word;
67 } ieee_float_shape_type;
68
69 /* Get a 32 bit int from a float. */
70 #ifndef GET_FLOAT_WORD
71 # define GET_FLOAT_WORD(i,d) \
72 do { \
73 ieee_float_shape_type gf_u; \
74 gf_u.value = (d); \
75 (i) = gf_u.word; \
76 } while (0)
77 #endif

然后做了一个与操作,0x7fffffff,为什么是这个数呢? 因为32位浮点数的表示中,第31位表示符号位,第30位到23位表示指数域,第22位到第0位表示小数域。 0x7fffffff截断了除符号位的其余数,即指数域+小数域;

  • 如果此时wx为0,那说明指数域+小数域都为0,也就返回FP_ZERO;
  • 如果wx < 0x800000(第23位为1),说明只有第0位到第22位有值,即小数域有值,但实际上指数域的范围是(1 – 254),不可能为0,所以返回FP_SUBNORMAL;
  • 如果wx == 0x7f800000,说明指数域占满了1,小数域全为0,这也是不可能的,标记为溢出FP_INFINITE
  • 如果wx > 0x7f800000,说明指数域全为1,小数域有数值,因为8位指数的取值范围为0255(0000000011111111),但在浮点数的阶码中,00000000与11111111被保留用作特殊情况,所以阶码可用范围只有1~254,总共有254个值,这样的数字是没法进行表示的,所以标识为FP_NAN(非数字)。 riscv的实现与其指令集相关,我们就不过多分析了。 __fpclassifyl函数的实现与__fpclassifyf类似,区别在于 64位浮点数1位符号位S,11位指数位E 和52位尾数位M

C++学习------cmath头文件的源码学习01---类型别名与宏函数_cmath_02

后续函数的分析见下一节

举报

相关推荐

0 条评论