C语言数学库的3种类型

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


C语言数学库的3种类型

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 
#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标记(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 
#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.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 
...
    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最简单的方式。

展开阅读全文

页面更新:2024-03-23

标签:圆括号   正切   平方根   数学   复数   编译器   弧度   表达式   指针   函数   种类   角度   定义   参数   语言

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top