C’de Standart Başlık Dosyaları ve Matematiksel Fonksiyonlar

“Tekerleği yeniden icat etmek” deyimini mutlaka duymuşsunuzdur. Bu deyim varolan bir şeyin aynısını veya benzerini tekrar yapıp kullanmaya çalışmak anlamına gelir ve genellikle alanlarında uzman kişiler bizlere “Tekerleği yeniden icat etmeyin!” diye tavsiyelerde bulunurlar. Elbette bunun doğru veya yanlış olduğu durumlar tartışılır. Ancak bu yazıda bu durumun programlama dillerinde nasıl bir etkisinin olduğunu ve buna nasıl bir çözüm bulunduğunu açıklamaya çalışacağım.

C ve diğer programlama dillerinde her şeyi kendiniz yazmaya kalktığınızda inanılmaz bir zaman kaybı yaşarsınız. Bu nedenle çoğu programlama dilinde belli işleri yerine getirmek için kullanabileceğiniz kütüphaneler (libraries) bulunur. Bu kütüphanelerde bulunan fonksiyonlar, veri yapıları veya makrolar işlerinizi hızlandırma konusunda size yardımcı olurlar. Bununla birlikte bu kütüphaneler çoğu zaman programınızın verimliliğine (efficiency) de katkıda bulunur. Kendi yazdığınız bir fonsiyon veya veri yapısının verimliliğini sağlamak için pek çok analiz ve teste ihtiyaç duyarsınız. Ancak bu kütüphaneler (çoğu zaman) bu analiz ve testlerden çoktan geçmiş olurlar.

C dilinde de kullanabilieceğiniz pek çok kütüphane bulunmaktadır ve bunlara C Standart Kütüphanesi (C Standard Library) adı verilmektedir. C nesne yönelimli bir programlama dili olmadığınıdan bu kütüphanelerin içerisinde sınıflar bulunmaz. Ancak fonksiyonlar (veya fonksiyon prototipleri), veri yapıları ve kullanışlı sabitler bulunur. Bu yapılar ile programlarınızı daha hızlı bir şekilde implement edebilirsiniz. Genellikle bu kütüphaneler belli başlık dosyaları ile size sunulurlar. Başlık dosyalarından (header files) daha önce bahsetmiştim. Bu dosyalar C’de kullanacağınız fonksiyon ve makro gibi yapıların bildirimlerini tutan “.h” uzantılı dosyalardır. İşte kütüphaneler de bu arayüz dosyalarını programınıza #include önişlemci bildirimi ile dahil ederek kullanılabilirler. Bu bildirimin iki farkı kullanımı bulunmaktadır:

#include <Dosya İsmi>

#include “Dosya İsmi”

Bu bildirim ile belirttiğiniz dosyanın içeriği, önişleme aşamasında kaynak kodunuza eklenmektedir. Aslında tek yaptığı iş sisteminizde bulunan bir dosyayı alıp kaynak kodunuza yapıştırmaktır. C dilinde genellikle burada “Dosya İsmi” yerine “.h” veya “.c” uzantılı dosya ismi, uzantısıyla birlikte yazılır. Elbette çift tırnak (“”) ve açılı parantez (<>) kullanımında bazı farklar bulunmaktadır. Açılı parantez kullanımında dosya önceden sistemde tanımlı olan veya derleyiciye bildirilmiş olan çeşitli dizinler içinde aranır. Çift tırnak kullanımında ise dosya o kaynak dosyanın bulunduğu dizinde aranır. Birazdan bahsedeceğimiz standart başlık dosyaları derleyicinin dizininde yer aldığından ve bu dizin önceden sisteme bildirildiğinden, genellikle bu dosyaları kaynak koda dahil etmek istediğimizde açılı parantez kullanılır.

C dili sadece söz diziminden ibaret değildir. Bunun yanında size kullanabileceğiniz bazı standart başlık dosyaları da sunar. Bu başlık dosyaları da yine çeşitli fonksiyonları, makroları, veri yapılarını ve sabitleri içermektedir. Elbette her bir başlık dosyası farklı bir kategorideki işe hizmet eder. Bu başlık dosyalarının “standart” olarak isimlendirilmesinin sebebi, onların platformdan bağımsız olarak herhangi bir C projesinde kullanılabiliyor olmalarından ve tabiki C standartlarında belirtilmelerinden kaynaklanır. C’de standart olarak bulunan çoğu başlık dosyasını ve bu dosyalarda ne gibi yapıların bulunduğunu şu şekilde bir tablo ile özetleyebiliriz (açıklama kısımlarının sonunda yer alan parantezlerde o başlık dosyasının hangi C standardı ile dile eklendiği belirtilmiştir):

Başlık DosyasıAçıklama
<assert.h>Programda hata ayıklamaya (debugging) yardımcı olacak çeşitli makro tanımlarını içerir.
<complex.h>Karmaşık sayılar (complex numbers) ile ilgili veri yapılarını ve fonksiyonları içerir. (C99)
<ctype.h>Karakterlerin belli özelliklerini test etmeye yarayan fonksiyonları içerir.
<errno.h>Hata durumlarını raporlayacak makro ve değişkenleri içerir.
<fenv.h>Ondalıklı sayıların durumlarını ve kontrol modlarını kontrol etmeye yarayan türleri, fonksiyonları ve makroları içerir. (C99)
<float.h>Ondalıklı sayı türlerinin sınır bilgilerini tanımlayan makroları içerir.
<inttypes.h>printf fonksiyonunda kullanılmak üzere çeşitli tamsayı türleri için format belirteci görevi gören makroları içerir. (C99)
<iso646.h>C dilinde kullanılan ve ISO/IEC 646 standardı dışında kalan karakterler için alternatif karakter tanımlarını içerir. (C95)
<limits.h>Temel türlerin sınır bilgilerini içeren makroları içerisinde barındırır.
<locale.h>Yerelleştirme ile ilgili çeşitli fonksiyonları içerir.
<math.h>Yaygın kullanılan matematiksel fonksiyonları içerir.
<setjmp.h>goto deyimi ile yapamadığımız ve aynı faaliyet alanında yer almayan kod atlamalarını (jump) gerçekleştirmeye yarayan fonksiyonları içerir.
<signal.h>Sinyal yakalama ile ilgili fonksiyonları ve türleri içerir.
<stdalign.h>alignas ve alignof makrolarının tanımlarını içerir. (C11)
<stdarg.h>Değişken sayıda argüman alan fonksiyonlar ile ilgili makro ve türleri içerir.
<stdatomic.h>Atomik türleri içerisinde barındırır. (C11)
<stdbool.h>Boolean türleri ile ilgili makroları içerir. (C99)
<stddef.h>Yaygın kullanılan makro tanımlarını içerir.
<stdint.h>Sabit boyutlu tamsayı türlerini ve bunlar ile ilgili sınır bilgilerini tanımlayan makroları içerisinde barındırır. (C99)
<stdio.h>Standart girdi/çıktı mekanizması ile ilgili türleri ve fonksiyonları içerir.
<stdlib.h>Bellek yönetimi, String dönüşümleri, rastgele sayı üretme ve birçok faydalı iş ile ilgili fonksiyonu içerisinde barındıran önemli bir standart başlık dosyasıdır.
<stdnoreturn.h>noreturn makro tanımını içerir. (C11)
<string.h>String işleme ile ilgili çeşitli fonksiyonları içerisinde barındırır.
<tgmath.h>Türden bağımsız matematiksel işlemler yapabilen fonksiyonları içerir. (C99)
<threads.h>Thread’ler ile alakalı çeşitli türleri ve fonksiyonları içerir. (C11)
<time.h>Zaman ve tarih ile ilgili çeşitli türleri ve fonksiyonları içerir.
<uchar.h>UTF-16 ve UTF-32 karakter kodlamaları ile ilgili fonksiyonları içerir. (C11)
<wchar.h>Genişletilmiş karakter seti ile ilgili fonksiyonları içerir. (C95)
<wctype.h>ctype.h başlık dosyasında yer alan fonksiyonların genişletilmiş karakter seti ile ilgili türevlerini içerir. (C95)

Bu arada yukarıda açıklama yaparken makro (macro) kavramından sürekli bahsettim. Bu kavramı aslında #define ile parametreleri önişleme esnasında çözümlenecek olan fonksiyonlar gibi düşünebiliriz. #define önişlemci bildirimi ile çeşitli sabitler tanımlayabildiğimiz gibi makrolar da tanımlayabiliriz. Örnek:

#define kare(x) (x)*(x)

Yukarıda tanımladığımız kare isimli makro, ona verilen sayının karesini almaktadır. Bu makroyu normal bir fonksiyon çağrısı yapar gibi çağırabiliriz:

int sonuc = kare(4);

Şimdilik makrolar ile ilgili bu kadarını bilmeniz yeterlidir. İleride önişlemci bildirimleri ile ilgili daha ayrıntılı bilgiler vereceğim. Şimdi standart başlık dosyalarını gördüğümüze göre bu ve bundan sonraki yazılarımızda bu başlık dosyalarında kullanılan çeşitli tür, makro ve fonksiyonları yavaş yavaş açıklamaya çalışacağım. Öncelikle en sık kullanılan başlık dosyalarından biri olan math.h dosyasını ve onun içerdiği matematiksel fonksiyonları anlatacağım. Bu başlık dosyasında matematikte sıklıkla karşımıza çıkan trigonometrik, logaritmik fonksiyonlar ve yuvarlama ile ilgili çeşitli fonksiyonlar yer almaktadır.

Bu başlık dosyasında çok fazla fonksiyon yer alır. Bunların bir çoğu C99 standartları ile dile eklenmiştir. Ben burada hepsinden değil, çok sık kullanılan bazılarından bahsedeceğim. Öncelikle C99 öncesinden beri varolan temel matematiksel fonksiyonlardan, onların bildirimlerinden, açıklamalarından ve kullanımlarından bahsedelim:

Fonksiyon PrototipiAçıklama (İşlem)Kullanım
double fabs(double x);|x|double res = fabs(-4.1); /* res = 4.1 */
double fmod(double x, double y);x mod ydouble res = fmod(5, 2); /* res = 1.0 */
double exp(double x);exdouble res = exp(4); /* res = 54.60 */
double log(double x);ln(x)double res = log(2); /* res = 0.693 */
double log10(double x);log10(x)double res = log10(10); /* res = 1.0 */
double pow(double x, double y);xydouble res = pow(4, 2); /* res = 16.0 */
double sqrt(double x);x1/2double res = sqrt(81); /* res = 9.0 */
double sin(double x);sin(x)double res = sin(1.57); /* res = 1.0 */
double cos(double x);cos(x)double res = cos(0); /* res = 1.0 */
double tan(double x);tan(x)double res = tan(0); /* res = 0.0 */
double asin(double x);arcsin(x)double res = asin(1); /* res = 1.57 */
double acos(double x);arccos(x)double res = acos(0); /* res = 1.57 */
double atan(double x);arctan(x)double res = atan(1); /* res = 0.78 */
double ceil(double x);⌈x⌉double res = ceil(7.8); /* res = 8.0 */
double floor(double x);⌊x⌋double res = floor(7.8); /* res = 7.0 */

C99 öncesinde standart kütüphanede yer alan matematiksel fonksiyonların hepsi parametrelerine double türünden değerler alıp yine double türünde değer geri döndürürlerdi. double türü o zamanlar dildeki en büyük byte genişliğine sahip tür olduğu için bu tercih edilmiştir. Ancak C99 ve sonrasında standart kütüphaneye bu fonksiyonların float ve long double türleri de eklenmiştir. Bunlardan birazdan bahsedeceğim. Şimdi bu fonksiyonlarla ilgili gerekli açıklamaları yapıp örnekler verelim.

fabs fonksiyonu parametresine aldığı değerin mutlak değerini geri döndürmektedir. Bu fonksiyon oldukça sık kullanılan bir matematiksel fonksiyondur ve anlaması da oldukça basittir. Bu fonksiyonun bir de int türünde işlem abs isimli bir türevi bulunmaktadır. abs fonksiyonu normalde stdlib.h başlık dosyasında bulunsa da math.h kütüphanesini kaynak kodunuza dahil ederek de onu kullanmanız mümkündür. Ancak genellikle derleyiciler böyle bir durumda uyarı vereceklerdir. fabs ve abs fonksiyonları ile ilgili şu örnekleri verebiliriz:

#include <stdio.h>
#include <stlib.h> /* abs */
#include <math.h> /* fabs */

double posValue = 4.4;
double negValue = -4.3;
double zeroValue = 0;
int intValue = -67;

printf("Positive: %f\n", fabs(posValue));
printf("Negative: %f\n", fabs(negValue));
printf("Zero: %f\n", fabs(zeroValue));
printf("Integer: %d", abs(intValue));
ÇIKTI
Positive: 4.400000
Negative: 4.300000
Zero: 0.000000
Integer: 67

Burada printf fonksiyonunda double değerleri yazdırmak için “%f” format belirtecini kullandığımıza dikkat edin. Aslında “%lf” format belirteci de bu iş için uygundur, ancak C99 öncesinde bu format belirteci bulunmadığından hata alırsınız. Şimdi de fmod fonksiyonundan kısaca bahsedelim. Bu fonksiyon basitçe “%” operatörünün yaptığı mod alma işlemini yapmaktadır. Ancak “%” operatörü ile birlikte float ve double gibi ondalıklı türleri kullanmanız hata ile sonuçlanacaktır. Bu fonksiyon ise ondalıklı sayı türleri ile kullanılmaya uygundur:

double res1 = 6.7 % 4; /* Derleme hatası!!! */
double res2 = fmod(6.7, 4); /* res2 = 2.70000 */

exp, log ve log10 gibi fonksiyonlar logaritmik işlemlerin yapılmasında kullanılrılar. exp fonksiyonuna verdiğiniz değer “e” ile belirtilen Euler sabitinin üssü olarak ele alınarak hesaplama yapılır. log fonksiyonu ise bir sayının “e” tabanında logaritmasını bulmaya yarar. log10 ise bir sayının 10 tabanında logaritmasını bulmaya yarar. C99 standartları ile beraber standart kütüphaneye bir sayının 2 tabanında logaritmasını almaya yarayan log2 fonksiyonu eklenmiştir. Örnekler:

double e = exp(1); /* e = 2.718282 */
double res1 = log(e); /* res1 = 1.000000 */
double res2 = log10(100); /* res2 = 2.000000 */
double res3 = log2(8); /* res3 = 3.000000 (Sadece C99 ve sonrası için geçerli!!!) */

Şimdi de yine en sık kullanılan iki fonksiyon olan pow ve sqrt fonksiyonlarından bahsedelim. pow fonksiyonu ilk parametresine verilen değerin ikinci parametresi derecesinden üssünü alır. sqrt ise basitçe bir sayının karekökünü almak için kullanılır. Şimdi girilen 3 sayının standart sapmasını hesaplayan bir program yazalım. Standart sapma bir veri kümesinin ortalama değerinden bütün değerleri sırasıyla çıkarıp sonuçların karelerini toplama ve toplamı veri kümesinde yer alan eleman sayısının 1 eksiğine bölerek sonucun karekökünü alma ile hesaplanır. Buna göre yazacağımız C programı aşağıdaki gibi olacaktır:

// Code 5.3.1: Code5_3_1.c
// 3 sayının standart sapmasını hesaplama

#include <stdio.h>
#include <math.h>

int main()
{
    int n = 3;
    double num1, num2, num3;

    // Kullanıcıdan 3 değer al:
    scanf("%lf", &num1);
    scanf("%lf", &num2);
    scanf("%lf", &num3);

    // Değerlerin ortalamasını hesapla:
    double mean = (num1 + num2 + num3)/n;

    // Değerlerin standart sapmasını hesapla:
    double stdDev = sqrt((pow(num1-mean, 2) + pow(num2-mean, 2) + pow(num3-mean, 2))/n);

    printf("Standard deviation: %lf\n", stdDev);

    return 0;
}
ÇIKTI
2
4
6
Standard deviation: 1.632993

Program biraz tuhaf görünebilir, ancak şuan diziler gibi konuları görmediğimizden şimdilik bununla idare etmemiz gerekir. Ayrıca yazılarımda şimdiye kadar kolay anlaşılabilirlik açısından Türkçe isimlendirmeler kullansam da artık İngilizce isimlendirmelere geçiş yapabileceğimizi düşünüyorum. Bu örnek kendi kendini açıklamakla birlikte pow ve sqrt fonksiyonlarının çalışma biçimlerini gösteren güzel bir örnektir. Burada bir not düşmekte fayda var; eğer sqrt fonksiyonuna negatif bir değer verirseniz, bu fonksiyon size -nan gibi bir çıktı döndürecektir. Bu çıktı aslında yine math.h kütüphanesinde tanımlı olan NAN sabitinin negatif değeridir. Bu sabit bunun gibi matematiksel olarak sorunlu durumları ele almak için kullanılır.

sin, cos ve tan gibi fonksiyonların kullanımı aslında matematikteki kullanımlarına benzerdir. Bunların tersi olan asin, acos ve atan fonksiyonları da yine matematikteki karşılıkları ile çalışırlar. Ancak burada dikkat etmeniz gereken şey, bu fonksiyonların (ters olmayanlar) derece cinsinden değil radyan cinsinden değer aldıklarıdır. Peki Pi sayısının çeşitli oranlarına karşılık gelen radyan cinsinden değerleri nasıl üretebiliriz? Pi sayısını el ile tanımlamak doğru bir yaklaşım mıdır? Aslında Euler sabiti ve Pi sayısı gibi önemli sabitler math.h dosyasında #define önişlemci bildirimi ile tanımlanmıştır:

# define M_E		2.7182818284590452354	/* e */
# define M_LOG2E	1.4426950408889634074	/* log_2 e */
# define M_LOG10E	0.43429448190325182765	/* log_10 e */
# define M_LN2		0.69314718055994530942	/* log_e 2 */
# define M_LN10		2.30258509299404568402	/* log_e 10 */
# define M_PI		3.14159265358979323846	/* pi */
# define M_PI_2		1.57079632679489661923	/* pi/2 */
# define M_PI_4		0.78539816339744830962	/* pi/4 */
# define M_1_PI		0.31830988618379067154	/* 1/pi */
# define M_2_PI		0.63661977236758134308	/* 2/pi */
# define M_2_SQRTPI	1.12837916709551257390	/* 2/sqrt(pi) */
# define M_SQRT2	1.41421356237309504880	/* sqrt(2) */
# define M_SQRT1_2	0.70710678118654752440	/* 1/sqrt(2) */

Bu sabitler C standartlarında yer almasa da çoğu implementasyonda yer almaktadır. Bu yüzden onları kullanırken dikkatli olmakta fayda var. Pi sayısı değerine sahip radyan aslında 180 dereceye karşılık gelir. Şimdi M_PI sabiti ile bu 3 trigonometrik fonksiyonun değerlerini bulalım:

double resSin = sin(M_PI/2); /* resSin = sin(90) = 1.0 */
double resCos = cos(M_PI/2); /* resCos = cos(90) = 0.0 */
double resTan = tan(M_PI/4); /* resTan = tan(45) = 1.0 */

Pi sayısı ile ilgili diğer sabitleri kullanarak değişik biçimlerde yazımlar yapabiliriz:

double resSin = sin(M_PI_2); /* resSin = sin(90) = 1.0 */
double resCos = cos(M_PI_2); /* resCos = cos(90) = 0.0 */
double resTan = tan(M_PI_4); /* resTan = tan(45) = 1.0 */

Şimdi de matematiksel hesaplamalarda oldukça işimize yarayacak olan floor ve ceil fonksiyonlarından bahsedelim. floor fonksiyonu parametresine verilen değeri ondan küçük ve ona en yakın olan tam sayıya yuvarlar. ceil fonksiyonu ise parametresine verilen değeri ondan büyük ve ona en yakın olan tam sayıya yuvarlar. Burada “tam sayı” dediğime takılmayın, çünkü sonuç yine double türünden dönecektir. Sadece virgülden sonra 0 değerleri olacaktır. Şimdi birkaç örnek ile bu fonksiyonları anlamaya çalışalım:

double resFloor1 = floor(1.65); /* resFloor1 = 1.0 */
double resFloor2 = floor(-6.8); /* resFloor2 = -7.0 */
double resFloor3 = floor(0.0); /* resFloor3 = 0.0 */
double resFloor4 = floor(3.0); /* resFloor4 = 3.0 */

double resCeil1 = ceil(1.65); /* resCeil1 = 2.0 */
double resCeil2 = ceil(-6.8); /* resCeil2 = -6.0 */
double resCeil3 = ceil(0.0); /* resCeil3 = 0.0 */
double resCeil4 = ceil(3.0); /* resCeil4 = 3.0 */

Bir de C99 standartları ile dile eklenen round fonksiyonundan bahsedelim. Bu fonksiyon parametresine verilen değerine en yakın olan tamsayı değerini geri döndürür. Fonksiyonun prototipi şöyledir:

double round(double arg)

Fonksiyonun kullanımı ve çıktıları ise şöyle olacaktır:

double res1 = round(5.6); /* res1 = 6.0 */
double res2 = round(5.2); /* res2 = 5.0 */
double res3 = round(5.5); /* res3 = 6.0 */
double res4 = round(-5.6); /* res4 = -6.0 */
double res5 = round(-5.2); /* res5 = -5.0 */
double res6 = round(-5.5); /* res6 = -6.0 */
double res7 = round(6.0); /* res7 = 6.0 */
double res8 = round(0.0); /* res8 = 0.0 */

5.5 gibi pozitif bir ara değer de yukarı yuvarlama, -5.5 gibi negatif bir ara değerde ise aşağı yuvarlama yapıldığına dikkat edin. Şu ana kadar gördüğümüz tüm matematiksel fonksiyonlar double değerler alıp yine double türünden bir değer geri döndürmekteydi. Ancak C99’dan sonra bu fonksiyonların float ve long double değer alıp döndüren türleri kütüphaneye eklenmiştir. float için fonksiyon adının sonuna “f” eklemek gerekirken, long double için ise “l” eklemek gerekmektedir. Şu ana kadar gördüğümüz fmod, exp ve sin gibi bütün fonksiyonların C99 ve sonrasında float ve long double türevleri bulunmaktadır. Örneğin log10 fonksiyonunun float türü için prototipi şöyledir:

float log10f(float arg)

Yine aynı fonksiyonun long double türü için prototipi şöyle olacaktır:

long double log10l(long double arg)

Bu yazım biçimi diğer tüm fonksiyonlar için de geçerlidir. C’de fonksiyonlara aşırı yükleme yapma (overloading) özelliği bulunmadığından, aynı fonksiyonun farklı türden türevleri için isimlerinde bu tip değişiklikleri sıklıkla görürsünüz. Ancak C++’ta aynı fonksiyon ismini kullanarak farklı parametre sayısında ve türünde çeşitli fonksiyonlar tanımlayabilirsiniz. Bu da her bir fonksiyonun farklı türevleri için kullanılan isimlerini aklınızda tutma zorunluluğunu ortadan kaldıracaktır.

çalıştık. Fonksiyonlar konusundaki bilgilerimizi pekiştirmek için, ileride de sıklıkla karşımıza çıkacak C’deki yaygın matematiksel fonksiyonları örneklerle açıklamaya çalıştık. En son ise bu fonksiyonların C99 ile gelen farklı türden türevlerine değindik. Bir sonraki yazıda standart fonksiyonlardan devam edecek ve C’de nasıl rastgele (veya sözde rastgele) sayı üretip kullanabileceğimizi anlatacağım.

5 4 votes
Article Rating
Subscribe
Bildir
guest

0 Yorum
Eskiler
En Yeniler Beğenilenler