泛型数学 (C99 起)
来自cppreference.com
头文件 <tgmath.h> 包含头文件 <math.h> 及 <complex.h>,并定义了几种泛型宏。这些宏会根据实参类型决定要调用的实际函数。
对于每个宏,在 <math.h> 无后缀版函数中,所对应的实数类型为 double 的形参,即是所谓的泛型形参。(例如,pow 的两个形参都是泛型形参,但 scalbn 只有第一个形参是泛型形参)
如下所述,使用 <tgmath.h> 宏时,传递给泛型形参的实参类型,会决定宏所选择的函数。若实参的类型与所选函数的形参类型不兼容,则行为未定义。(例如,若将复数实参传入实数限定的 <tgmath.h> 宏: float complex fc; ceil(fc) 或 double complex dc; double d; fmax(dc, d) 就是未定义行为的例子)
注意:泛型宏在 C99 中曾以实现定义行为实现,但 C11 关键词 _Generic 使得以可移植方式实现这些宏成为可能。
复数/实数泛型宏
对于所有拥有实数及复数对应的函数,存在泛型宏,调用下列函数之一:
- 实数函数:
- float 变体
XXXf - double 变体
XXX - long double 变体
XXXl
- float 变体
- 复数函数:
- float 变体
cXXXf - double 变体
cXXX - long double 变体
cXXXl
- float 变体
上述规则的一个例外是 fabs 宏(见下表)。
按以下方式决定调用的函数:
- 若泛型形参的任一实参为虚数,则行为会在每个函数参考页面上各自指定。(具体而言,
sin、cos、tan、sinh、cosh、tanh、asin、atan、asinh及atanh调用实数函数,sin、cos、tan、sinh、tanh、asin、atan、asinh及atanh的返回类型是虚数,而cosh与cosh的返回类型是实数) - 若泛型形参的任一实参为复数,则复数函数会得到调用,否则会调用实数函数。
- 若泛型形参的任一实参为 long double,则调用 long double 变体。否则,若任一实参是 double 或整数,则调用 double 变体。否则会调用 float 变体。
泛型宏如下所示:
实数限定函数
对于所有无复数对应的函数,除 modf 外都存在泛型宏 XXX,它会调用实数函数变体的中的一种:
- float 变体
XXXf - double 变体
XXX - long double 变体
XXXl
以下列方式确定调用的函数:
- 若泛型形参的任一实参为 long double,则调用 long double 变体。否则,若泛型形参的任一实参是 double,则调用 double 变体。否则调用 float 变体。
| 泛型宏 | 实数函数变体 | ||
|---|---|---|---|
| float | double | long double | |
| atan2 | atan2f | atan2 | atan2l |
| cbrt | cbrtf | cbrt | cbrtl |
| ceil | ceilf | ceil | ceill |
| copysign | copysignf | copysign | copysignl |
| erf | erff | erf | erfl |
| erfc | erfcf | erfc | erfcl |
| exp2 | exp2f | exp2 | exp2l |
| expm1 | expm1f | expm1 | expm1l |
| fdim | fdimf | fdim | fdiml |
| floor | floorf | floor | floorl |
| fma | fmaf | fma | fmal |
| fmax | fmaxf | fmax | fmaxl |
| fmin | fminf | fmin | fminl |
| fmod | fmodf | fmod | fmodl |
| frexp | frexpf | frexp | frexpl |
| hypot | hypotf | hypot | hypotl |
| ilogb | ilogbf | ilogb | ilogbl |
| ldexp | ldexpf | ldexp | ldexpl |
| lgamma | lgammaf | lgamma | lgammal |
| llrint | llrintf | llrint | llrintl |
| llround | llroundf | llround | llroundl |
| log10 | log10f | log10 | log10l |
| log1p | log1pf | log1p | log1pl |
| log2 | log2f | log2 | log2l |
| logb | logbf | logb | logbl |
| lrint | lrintf | lrint | lrintl |
| lround | lroundf | lround | lroundl |
| nearbyint | nearbyintf | nearbyint | nearbyintl |
| nextafter | nextafterf | nextafter | nextafterl |
| nexttoward | nexttowardf | nexttoward | nexttowardl |
| remainder | remainderf | remainder | remainderl |
| remquo | remquof | remquo | remquol |
| rint | rintf | rint | rintl |
| round | roundf | round | roundl |
| scalbln | scalblnf | scalbln | scalblnl |
| scalbn | scalbnf | scalbn | scalbnl |
| tgamma | tgammaf | tgamma | tgammal |
| trunc | truncf | trunc | truncl |
复数限定函数
对于所有没有实数对应的复数函数,存在泛型宏 cXXX,它会调用复数函数的变体:
调用的函数按以下方式决定:
- 若泛型形参的任一实参为实数、复数或虚数,则调用适当的复数函数。
| 泛型宏 | 复数函数变体 | ||
|---|---|---|---|
| float | double | long double | |
| carg | cargf | carg | cargl |
| conj | conjf | conj | conjl |
| creal | crealf | creal | creall |
| cimag | cimagf | cimag | cimagl |
| cproj | cprojf | cproj | cprojl |
示例
运行此代码
#include <stdio.h> #include <tgmath.h> int main(void) { int i = 2; printf("sqrt(2) = %f\n", sqrt(i)); // 实参类型为 int,调用 sqrt float f = 0.5; printf("sin(0.5f) = %f\n", sin(f)); // 实参类型为 float,调用 sinf float complex dc = 1 + 0.5*I; float complex z = sqrt(dc); // 实参类型为 float complex,调用 csqrtf printf("sqrt(1 + 0.5i) = %f+%fi\n", creal(z), // 实参类型为 float complex,调用 crealf cimag(z)); // 实参类型为 float complex,调用 cimagf }
输出:
sqrt(2) = 1.414214 sin(0.5f) = 0.479426 sqrt(1 + 0.5i) = 1.029086+0.242934i
引用
- C23 标准(ISO/IEC 9899:2024):
- 7.25 Type-generic math <tgmath.h> (第 TBD 页)
- C17 标准(ISO/IEC 9899:2018):
- 7.25 Type-generic math <tgmath.h> (第 272-273 页)
- C11 标准(ISO/IEC 9899:2011):
- 7.25 Type-generic math <tgmath.h> (第 373-375 页)
- C99 标准(ISO/IEC 9899:1999):
- 7.22 Type-generic math <tgmath.h> (第 335-337 页)