数学库包含许多有用的数学函数。 math.h 头文件提供了这些函数的原型。 表 16.2 列出了 math.h 中声明的一些函数。 请注意,函数中涉及的角度以弧度为单位(1 弧度 = 180/π = 57.296 度)。 参考文献V《New ANSI C for C99 and C11 》列出了C99和C11标准的所有函数。
一些 ANSI C 数学
1 三角形问题
我们可以使用数学库来解决一些常见问题:将 x/y 坐标转换为长度和角度。 例如,在网格上绘制一条线,水平穿过 4 个单元格(x 的值),垂直穿过 3 个单元格(y 的值)。 那么,该线的长度(大小)和方向是多少? 根据数学三角公式,我们可以知道:
magnitude = square root (x^2 + y^2)
和
angle = arctangent (y/x)
数学库提供了一个平方根函数和一对反正切函数,因此这个问题可以用C程序来表达。 平方根函数是 sqrt(),它接受一个类型的参数并返回该类型的参数的平方根。 atan()函数接受一种类型的参数(即正切值)并返回一个角度(角度的正切值即为参数值)。 然而,当该线的 x 和 y 值均为 -5 时,atan() 函数会产生混乱。 因为 (-5)/(-5) 为 1,所以 atan() 返回 45°,这与 x 和 y 均为 5 时返回的值相同。也就是说,atan() 无法区分具有相同角度的线。角度但方向相反(实际上atan()返回值的单位是弧度而不是度,后面会介绍两者之间的转换)。 当然,C库还提供了atan2()函数。 它接受两个参数:x 的值和y 的值。 这样,通过检查x和y的符号就可以得到正确的角度值。 atan2() 和 atan() 都返回弧度值。 要将弧度转换为度数,只需将弧度值乘以 180,然后除以 pi。 pi 的值通过计算表达式 4*atan(1) 获得。 .c 演示了这些步骤。 另外,学习程序还要复习结构和相关知识。
.c
#include
#include
#define RAD_TO_DEG (180/(4 * atan(1)))
typedef struct polar_v {
double magnitude;
double angle;
} Polar_V;
typedef struct rect_v {
double x;
double y;
} Rect_V;
Polar_V rect_to_polar(Rect_V);
int main(void)
{
Rect_V input;
Polar_V result;
puts("Enter x and y coordinates; enter q to quit:");
while (scanf("%lf %lf", &input.x, &input.y) == 2)
{
result = rect_to_polar(input);
printf("magnitude = %0.2f, angle = %0.2fn",
result.magnitude, result.angle);
}
puts("Bye.");
return 0;
}
Polar_V rect_to_polar(Rect_V rv)
{
Polar_V pv;
pv.magnitude = sqrt(rv.x * rv.x + rv.y * rv.y);
if (pv.magnitude == 0)
pv.angle = 0.0;
else
pv.angle = RAD_TO_DEG * atan2(rv.y, rv.x);
return pv;
}
以下是运行程序后的示例输出:
Enter x and y coordinates; enter q to quit:
10 10
magnitude = 14.14, angle = 45.00
-12 -5
magnitude = 13.00, angle = -157.38
q
Bye.
如果编译时出现如下信息:
Undefined: _sqrt
或者
'sqrt': unresolved external
或其他类似消息,表明编译器链接器未找到数学库。 UNIX 系统将需要 -lm 标志来指示链接器搜索数学库:
cc rect_pol.c --lm
请注意,-lm 标志位于命令行末尾。 因为链接器在编译器编译完C文件后才开始处理。 在Linux中使用GCC编译器可能会这样写:
gcc rect_pol.c -lm
2种类型的变体
基本浮点数学函数接受类型的参数并返回类型的值。 当然,你也可以向这些函数传递float或long类型的参数,它们仍然会正常工作,因为这些类型的参数会被转换为类型。 这很方便,但不是最好的做事方式。 如果不需要双精度,使用float类型的单精度值计算会更快。 而且,将long类型的值传递给该类型的形参会丢失精度,形参得到的值可能不是原来的值。 为了解决这些潜在的问题,C标准专门针对float类型和long类型提供了标准函数,即在原始函数名后添加f或l后缀。 因此,sqrtf() 是 sqrt() 的浮点版本,而 sqrtl() 是 sqrt() 的版本。 使用C11新的通用选择表达式来定义通用宏,并根据参数类型选择最合适的数学函数版本。 清单 16.15 演示了这两种方法。
16.15 .c
// generic.c -- defining generic macros
#include
#include
#define RAD_TO_DEG (180/(4 * atanl(1)))
// generic square root function
#define SQRT(X) _Generic((X),
long double: sqrtl,
default: sqrt,
float: sqrtf)(X)
// generic sine function, angle in degrees
#define SIN(X) _Generic((X),
long double: sinl((X)/RAD_TO_DEG),
default: sin((X)/RAD_TO_DEG),
float: sinf((X)/RAD_TO_DEG)
)
int main(void)
{
float x = 45.0f;
double xx = 45.0;
long double xxx =45.0L;
long double y = SQRT(x);
long double yy= SQRT(xx);
long double yyy = SQRT(xxx);
printf("%.17Lfn", y); // matches float
printf("%.17Lfn", yy); // matches default
printf("%.17Lfn", yyy); // matches long double
int i = 45;
yy = SQRT(i); // matches default
printf("%.17Lfn", yy);
yyy= SIN(xxx); // matches long double
printf("%.17Lfn", yyy);
return 0;
}
这是程序的输出:
6.6.6.6.0.
如上所示,SQRT(i)和SQRT(xx)的返回值是相同的,因为它们的参数类型分别是int和,所以只能对应标签。 有趣的是如何使宏表现得像函数一样。 SIN() 的定义可能提供了一种方式:每个标记值都是一个函数调用,因此表达式的值是一个具体的函数调用,例如 sinf((X)/),将值传递给 SIN() 参数来替换X。SQRT()的定义可能更简洁。 表达式的值是函数名,例如sinf。 函数的地址可以代替函数名,因此表达式的值是指向函数的指针。 然而,在整个表达式之后是表示函数指针的函数指针(参数)(X)。 因此,这是一个带有指定参数的函数指针。 简而言之,对于 SIN(),函数调用位于通用选择表达式内部; 对于 SQRT(),通用选择表达式被计算为指针,并通过该指针调用它指向的函数。
3.h库(C99)
泛型类型宏定义在C99标准提供的.h头文件中,其效果与程序清单16.15类似。 如果在 math.h 中定义了函数的 3 种类型(浮点型和长整型)版本,则 .h 文件将创建一个与原始版本的函数名称同名的泛型类型宏。 例如,定义 sqrt() 宏以扩展到 sqrtf()、sqrt() 或 sqrtl() 函数,具体取决于提供的参数类型。 换句话说,sqrt() 宏的行为类似于清单 16.15 中的 SQRT() 宏。 如果编译器支持复杂算术,它将支持.h头文件,该文件声明与复杂算术相关的函数。 例如,声明了 ()、csqrt() 和 (),这些函数分别返回 float 和 long 类型的复数平方根。 如果提供这些支持,.h 中的 sqrt() 宏也可以扩展为相应的复数平方根函数。 如果包含 .h,要调用 sqrt() 函数而不是 sqrt() 宏,请将调用的函数名称括在括号中:
#include
...
float x = 44.0;
double y;
y = sqrt(x); // invoke macro, hence sqrtf(x)
y = (sqrt)(x); // invoke function sqrt()
这很好用,因为类似函数的宏的名称必须括在括号中。 括号只影响运算顺序,而不影响括起来的表达式,因此通过这种方式得到的仍然是函数调用的结果。 事实上,正如在函数指针的讨论中提到的,由于C语言奇怪且矛盾的函数指针规则,sqrt()函数也可以以(*sqrt)()的形式调用。 C11 的新表达式是实现 .h 的最简单方法,无需求助于 C 标准之外的机制。