數學庫中包含許多有用的數學函數。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 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
基本的浮點型數學函數接受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最簡單的方式。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!