当前位置:首页 > 技术分析 > 正文内容

C语言数学库的3种类型

ruisui882个月前 (03-05)技术分析8

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


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最简单的方式。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/2507.html

标签: degrees函数
分享给朋友:

“C语言数学库的3种类型” 的相关文章

Linux发行版Debian推出12.2及11.8版本,修复多个安全问题

IT之家 10 月 9 日消息,Debian 是最古老的 GNU / Linux 发行版之一,也是许多其他基于 Linux 的操作系统的基础,包括 Ubuntu、Kali、MX 和树莓派 OS 等,近日 Debian 推出了 12.2 和 11.8 版本,主要修复了多个安全问题。▲ 图源 Debia...

红帽最新的企业 Linux 发行版具有解决混合云复杂性的新功能

据zdnet网5月1日报道,红帽这家 Linux 和超云领导者今天发布了其最新的旗舰 Linux 发行版 Red Hat Enterprise Linux (RHEL) 9.4,此前上周宣布对已有十年历史的流行 RHEL 7.9 再支持四年。这个领先的企业 Linux 发行版的最新版本引入了许多新功...

vue中组件之间的通信方式

** 1.1 父子组件**a. 父向子传数据: 第1种: 父通过属性传值,子组件通过props接收数据(注:props传过来的数据是单向的,不可以进行修改)第2种:子组件可以通过$parent来获取父组件里的数据和调用父组件的方法(注:数据是双向的,还要注意如用了UI组件并且在该UI组件里重新定义一...

22《Vue 入门教程》VueRouter 路由嵌套

1. 前言本小节我们介绍如何嵌套使用 VueRouter。嵌套路由在日常的开发中非常常见,如何定义和使用嵌套路由是本节的重点。同学们在学完本节课程之后需要自己多尝试配置路由。2. 配置嵌套路由实际项目中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层...

Vue中路由router的基本使用

??本文开始我们来给大家介绍在Vue中非常重要的一个内容,就是路由Router什么是路由后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特...

Vue Router 4 路由操作 - 路由导航

路由导航分为 声明式导航 和 编程式导航。通过 <router-link to="..."> 标签跳转的方式为声明式导航。通过 路由实例对象(router.push(...))跳转的为编程式导航。导航到不同的位置想要导航到不同的URL,使用 router.push 方法。...