Menu

Funkcje matematyczne w C: math.h, sqrt, pow i linkowanie z -lm

Przegląd math.h: sqrt, pow, fabs, floor i ceil, round, fmod, rodziny funkcji trygonometrycznych i logarytmów, INFINITY i NAN, a do tego flaga linkera -lm, przez którą pojawia się klasyczny błąd undefined reference.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Sam język C daje ci + - * / i %, i nic więcej: nie ma operatora potęgowania, pierwiastka ani zaokrąglania. Wszystko poza arytmetyką leży w standardowej bibliotece matematycznej, zadeklarowanej w <math.h>.

Wszystkie te funkcje działają z precyzją double: przyjmują argumenty double i zwracają double. To pierwsza rzecz do zapamiętania, bo przekazanie int działa po cichu (następuje konwersja), a przypisanie wyniku do int po cichu odrzuca część ułamkową.

Pierwsze kroki i flaga -lm

Na twoim komputerze kompilacja tego kodu może się nie udać:

/usr/bin/ld: /tmp/ccXYZ.o: in function `main':
program.c:(.text+0x1a): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

To najczęstsze potknięcie z math.h i nie jest to błąd w twoim kodzie. Nagłówek dostarczył deklaracje, więc kompilator był zadowolony; implementacje leżą w osobnej bibliotece (libm), której linker na Linuksie domyślnie nie dołącza. Dodaj -lm:

gcc program.c -o program -lm

Flaga musi stać po plikach źródłowych: linkery przetwarzają wejścia po kolei, a biblioteka podana przed kodem, który jej potrzebuje, niczego nie rozwiąże. Na macOS i z MinGW na Windows funkcje matematyczne są już częścią standardowej biblioteki C, więc -lm jest zbędne (choć na macOS nieszkodliwe).

Potęgi i pierwiastki

sqrt(x) z liczby ujemnej zwraca NaN, zamiast się wysypać; zabezpiecz argument, jeśli może być ujemny. hypot warto znać, bo oblicza tę samą wartość co sqrt(x*x + y*y) bez przepełnienia, gdy x albo y jest bardzo duże.

Dla małych potęg całkowitych zwykłe mnożenie jest i szybsze, i dokładne: x * x wygrywa z pow(x, 2.0), które przechodzi przez logarytmy i może zwrócić 8.999999999999998 tam, gdzie spodziewasz się 9.

Zaokrąglanie: floor, ceil, round, trunc

Cztery funkcje, cztery różne odpowiedzi dla liczb ujemnych. Tę tabelę warto zapamiętać:

  • floor(x): największa liczba całkowita nie większa niż x. Zawsze przesuwa w stronę minus nieskończoności: floor(-2.3) to -3.
  • ceil(x): najmniejsza liczba całkowita nie mniejsza niż x. Zawsze przesuwa w stronę plus nieskończoności: ceil(-2.3) to -2.
  • round(x): najbliższa liczba całkowita, połówki od zera: round(2.5) to 3, round(-2.5) to -3.
  • trunc(x): obcina część ułamkową, przesuwając w stronę zera: trunc(-2.7) to -2.

Wszystkie cztery zwracają double. Rzutowanie na int, gdy potrzebujesz liczby całkowitej, jest w porządku, ale pamiętaj, że samo (int)x obcina, więc (int)(x + 0.5) to stary idiom "zaokrąglania", który jest błędny dla liczb ujemnych. Używaj round().

fabs i fmod

fabs to wartość bezwzględna dla liczb zmiennoprzecinkowych. Sięgnięcie zamiast niej po abs to klasyczny błąd, bo abs przyjmuje int, a konwersja dzieje się po cichu:

% w ogóle nie działa na double: to operator całkowity i kompilator go odrzuci. fmod(a, b) to jego odpowiednik zmiennoprzecinkowy i zachowuje znak a.

fabs to też właściwy sposób porównywania dwóch liczb double, bo == na liczbach zmiennoprzecinkowych to pułapka:

0.1 i 0.2 nie dają się dokładnie zapisać binarnie, więc ich suma różni się od 0.3 o włos. Porównuj z tolerancją, nigdy przez ==.

Trygonometria

Funkcje trygonometryczne działają w radianach, a nie stopniach, i to drugie niezawodne źródło złych odpowiedzi.

Pełny zestaw: sin, cos, tan, ich odwrotności asin, acos, atan, dwuargumentowe atan2(y, x) i rodzina hiperboliczna sinh, cosh, tanh. Do kątów wybieraj atan2(y, x) zamiast atan(y / x): obsługuje x == 0 i poprawnie ustala ćwiartkę.

math.h w większości systemów definiuje M_PI, ale to rozszerzenie POSIX, a nie standardowe C, więc przy -std=c17 może go brakować. Zdefiniowanie własnej stałej, jak powyżej, jest przenośne.

Logarytmy i funkcje wykładnicze

Zwróć uwagę na pułapkę w nazewnictwie: log() to logarytm naturalny (podstawa e), a nie dziesiętny. Podstawa 10 to log10().

log(0.0) zwraca minus nieskończoność, a log(-1.0) zwraca NaN. Żadne z nich nie powoduje awarii, więc zły argument po cichu wędruje przez obliczenia, aż coś wypisze nan.

INFINITY, NAN i ich sprawdzanie

Liczby zmiennoprzecinkowe mają wartości, które nie są liczbami, a math.h nadaje im nazwy:

Kluczowa jest ostatnia linia. Nie możesz napisać if (x == NAN), bo to zawsze fałsz. Użyj isnan(x). (Tożsamość x != x jest prawdziwa tylko dla NaN; często tak właśnie implementuje się isnan i to przydatne wyjście awaryjne, gdyby kiedyś trafił się zestaw narzędzi bez tej funkcji.)

Nieskończoności biorą się z przepełnienia (exp(1000.0)) i z dzielenia niezerowej liczby double przez zero, które w odróżnieniu od całkowitego dzielenia przez zero jest zdefiniowane i nie powoduje awarii.

Warianty float i long double

Każda funkcja ma trzy formy: wersję double, wersję float z przyrostkiem f i wersję long double z przyrostkiem l.

float       sqrtf(float x);
double      sqrt(double x);
long double sqrtl(long double x);

Używaj zwykłych wersji double, chyba że masz zmierzony powód, żeby tego nie robić: C domyślnie promuje do double, a wplatanie wariantów float zwykle kosztuje więcej na konwersjach, niż oszczędza. Przyrostek ma znaczenie w jednym miejscu: gdy obliczenia wyłącznie na float są krytyczne dla wydajności na sprzęcie bez obsługi double.

Praktyczny przykład

Kilka z tych funkcji razem: odległość między dwoma punktami i pierwiastki równania kwadratowego:

Zauważ fabs(disc) < 1e-12 zamiast disc == 0.0: ta sama zasada tolerancji co wcześniej, zastosowana tam, gdzie wyróżnik obliczony z mierzonych wartości prawie nigdy nie wypadnie dokładnie na zerze.

<math.h> to jeden nagłówek z większego zestawu narzędzi; resztę opisuje strona o bibliotece standardowej, a strona o liczbach losowych omawia rand, które leży w <stdlib.h>, a nie tutaj.

Najczęściej zadawane pytania

Jak używać funkcji matematycznych w C?

Dodaj #include <math.h> na początku pliku i wywołuj je: sqrt(16.0), pow(2.0, 10.0), fabs(-3.5). Przyjmują i zwracają double. Na Linuksie trzeba też dolinkować bibliotekę matematyczną przez -lm: gcc program.c -o program -lm.

Skąd błąd 'undefined reference to sqrt' przy kompilacji?

Nagłówek dał kompilatorowi deklarację, ale implementacja leży w osobnej bibliotece, której linker na Linuksie nie dołącza automatycznie. Dodaj -lm na końcu polecenia: gcc program.c -o program -lm. Na macOS i z MinGW na Windows kod matematyczny jest częścią standardowej biblioteki C, więc flaga nie jest potrzebna.

Czym różni się abs od fabs w C?

abs() z <stdlib.h> przyjmuje int i zwraca int. fabs() z <math.h> przyjmuje double i zwraca double. Wywołanie abs(-3.7) najpierw zamienia argument na int i daje 3, po cichu odrzucając część ułamkową. Dla wartości zmiennoprzecinkowych używaj fabs.

Jak sprawdzić NaN w C?

Użyj isnan(x) z <math.h>. Nie da się tego sprawdzić przez x == NAN, bo NaN jest różne od wszystkiego, łącznie z samym sobą. Ta osobliwość to zresztą test zapasowy: x != x jest prawdą tylko dla NaN. Do nieskończoności używaj isinf(x), a isfinite(x) sprawdza, czy to zwykła liczba.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ