推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

C语言数学库的3种类型

   2023-08-22 网络整理佚名1560
核心提示:数学库中包含许多有用的数学函数。平方根函数是sqrt(),接受一个类型的参数,并返回参数的平方根,也是类型。atan()函数接受一个类型的参数(即正切值),并返回一个角度(该角度的正切值就是参数值)。基本的浮点型数学函数接受类型的参数,并返回类型的值。表达式的值就是函数名,如sinf。函数的地址可以代替该函数名,所以表达式的值是一个指向函数的指针。

数学库包含许多有用的数学函数。 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 标准之外的机制。

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON