龙空技术网

C语言数学库的3种类型

龙骑士洞察 406

前言:

当前看官们对“c语言平方根怎么表示”大约比较珍视,姐妹们都需要知道一些“c语言平方根怎么表示”的相关知识。那么小编同时在网摘上收集了一些有关“c语言平方根怎么表示””的相关内容,希望小伙伴们能喜欢,大家快快来学习一下吧!

数学库中包含许多有用的数学函数。math.h头文件提供这些函数的原型。表16.2中列出了一些声明在math.h中的函数。注意,函数中涉及的角度都以弧度为单位(1弧度=180/π=57.296度)。参考资料V“新增C99和C11标准的ANSI C库”列出了C99和C11标准的所有函数。

Some ANSI C Standard Math Functions

1 三角问题

我们可以使用数学库解决一些常见的问题:把x/y坐标转换为长度和角度。例如,在网格上画了一条线,该线条水平穿过了4个单元(x的值),垂直穿过了3个单元(y的值)。那么,该线的长度(量)和方向是什么?根据数学的三角公式可知:

magnitude = square root (x^2 + y^2)

and

angle = arctangent (y/x)

数学库提供平方根函数和一对反正切函数,所以可以用C程序表示这个问题。平方根函数是sqrt(),接受一个double类型的参数,并返回参数的平方根,也是double类型。 atan()函数接受一个double类型的参数(即正切值),并返回一个角度(该角度的正切值就是参数值)。但是,当线的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)得到。程序rectpol.c演示了这些步骤。另外,学习该程序还复习了结构和typedef相关的知识。

The rectpol.c Program

/* rect_pol.c -- converts rectangular coordinates to polar */#include <stdio.h>#include <math.h>​#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 10magnitude = 14.14, angle = 45.00-12 -5magnitude = 13.00, angle = -157.38qBye.

如果编译时出现下面的消息:

Undefined:     _sqrt

或者

'sqrt': unresolved external

或者其他类似的消息,表明编译器链接器没有找到数学库。UNIX系统会要求使用-lm标记(flag)指示链接器搜索数学库:

cc rect_pol.c --lm

注意,-lm标记在命令行的末尾。因为链接器在编译器编译C文件后才开始处理。在Linux中使用GCC编译器可能要这样写:

gcc rect_pol.c -lm
2 类型变体

基本的浮点型数学函数接受double类型的参数,并返回double类型的值。当然,也可以把float或long double类型的参数传递给这些函数,它们仍然能正常工作,因为这些类型的参数会被转换成double类型。这样做很方便,但并不是最好的处理方式。如果不需要双精度,那么用float类型的单精度值来计算会更快些。而且把long double类型的值传递给double类型的形参会损失精度,形参获得的值可能不是原来的值。为了解决这些潜在的问题,C标准专门为float类型和long double类型提供了标准函数,即在原函数名后加上f或l后缀。因此,sqrtf()是sqrt()的float版本,sqrtl()是sqrt()的longdouble版本。 利用C11新增的泛型选择表达式定义一个泛型宏,根据参数类型选择最合适的数学函数版本。程序清单16.15演示了两种方法。

Listing 16.15 The generic.c Program

​//  generic.c  -- defining generic macros​#include <stdio.h>#include <math.h>#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.70820379257202148 6.70820393249936942 6.70820393249936909 6.70820393249936942 0.70710678118654752

如上所示,SQRT(i)和SQRT(xx)的返回值相同,因为它们的参数类型分别是int和double,所以只能与default标签对应。 有趣的一点是,如何让Generic宏的行为像一个函数。SIN()的定义也许提供了一个方法:每个带标号的值都是函数调用,所以Generic表达式的值是一个特定的函数调用,如sinf((X)/RADTODEG),用传入SIN()的参数替换X。 SQRT()的定义也许更简洁。Generic表达式的值就是函数名,如sinf。函数的地址可以代替该函数名,所以Generic表达式的值是一个指向函数的指针。然而,紧随整个Generic表达式之后的是(X),函数指针(参数)表示函数指针。因此,这是一个带指定的参数的函数指针。 简而言之,对于SIN(),函数调用在泛型选择表达式内部;而对于SQRT(),先对泛型选择表达式求值得一个指针,然后通过该指针调用它所指向的函数。

3 tgmath.h库(C99)

C99标准提供的tgmath.h头文件中定义了泛型类型宏,其效果与程序清单16.15类似。如果在math.h中为一个函数定义了3种类型(float、double和long double)的版本,那么tgmath.h文件就创建一个泛型类型宏,与原来double版本的函数名同名。例如,根据提供的参数类型,定义sqrt()宏展开为sqrtf()、sqrt()或sqrtl()函数。换言之,sqrt()宏的行为和程序清单16.15中的SQRT()宏类似。 如果编译器支持复数运算,就会支持complex.h头文件,其中声明了与复数运算相关的函数。例如,声明有csqrtf()、csqrt()和csqrtl(),这些函数分别返回float complex、double complex和long double complex类型的复数平方根。如果提供这些支持,那么tgmath.h中的sqrt()宏也能展开为相应的复数平方根函数。 如果包含了tgmath.h,要调用sqrt()函数而不是sqrt()宏,可以用圆括号把被调用的函数名括起来:

#include <tgmath.h>...    float x = 44.0;    double y;    y = sqrt(x);   // invoke macro, hence sqrtf(x)    y = (sqrt)(x); // invoke function sqrt()

这样做没问题,因为类函数宏的名称必须用圆括号括起来。圆括号只会影响操作顺序,不会影响括起来的表达式,所以这样做得到的仍然是函数调用的结果。实际上,在讨论函数指针时提到过,由于C语言奇怪而矛盾的函数指针规则,还可以使用(*sqrt)()的形式来调用sqrt()函数。不借助C标准以外的机制,C11新增的Generic表达式是实现tgmath.h最简单的方式。

标签: #c语言平方根怎么表示