C99 základní informace o standardu programovacího jazyka C Výpočty v přesné aritmetice Josef Dobeš Katedra radioelektroniky (13137), blok B2, místnost 722 dobes@fel.cvut.cz 15. května 2017 Praktické programování v C/C++ (České vysoké učení technické v Praze, Fakulta elektrotechnická) 1
1 Standardizace prvků jazyka ne dříve vždy povinných V nejrůznějších překladačích jazyka C se dříve víceméně dobrovolně dodržovaly určité obvyklé rysy, které sice striktně nebyly stanoveny standardy (jako je např. ANSI), nicméně většina překladačů je dodržovala. V důsledku toho se ovšem občas objevovaly problémy s přenositelností kódu odladěného v jednom překladači k použití pro překladač jiný. Standard C99 je poměrně rozsáhlý (texty standardu výrazně překračují 100 stran) a řeší tak mnoho problémů s kompatibilitou překladačů. Jako typický příklad lze uvést formátové konverze v standardních funkcích printf, fprintf, sprintf apod. Většina formátových konverzí jako jsou např. %d, %f pracuje samozřejmě stejně, ovšem časté potíže nastávaly v dodatečných specifikacích, např. pro long double apod. Jako příklad zde uvedeme konverzi %Lf pro výstup long double, která již před přijetím standardu C99 fungovala ve většině překladačů. Nepracovala však správně v překladači GNU GCC (od jistých verzí) GCC totiž přejal kódy od Microsoft a ten (stejně jako Borland) standard C99 dosud plně nepodporuje (obě firmy jsou v současnosti zaměřeny především na C++). Pokud tedy chceme konverzí %Lf vypisovat long double, musíme jako option v překladači dát option -std=c99 (viz nastavení Code::Blocks), jinak se long double vypisuje nesprávně. V následujícím zdrojovém souboru si povšimněme obvyklého způsobu získání přesnosti epsilon porovnáváním její postupně se zmenšující velikosti přičtené k jedné s jednou. Dále si povšimněme, že aby konstanta byla chápána jako long double, musí mít příponu (suffix) L (jinak je chápána jako double). Pro výpočet π si rovněž všimněme nezbytnosti použít long double verze funkce arkus tangens atanl, pro kterou je vstupem a výstupem číslo long double. 2
Zdrojový soubor pro test přesnosti, který funguje pro Digital Mars i GNU GCC (a i pro jiné): #include <stdio.h> #include <math.h> typedef long double real; int main() { real a = 1, b1 = 3.1415926535897932384626434, // from Wikipedia to 25 decimal places b2 = 3.1415926535897932384626434L; // b1 is double, b2 is long double // epsilon computing while (1 + a/2!= 1) a /= 2; printf("length of 'long double' = %d, epsilon = %Lg\n", sizeof(a), a); // pi computing printf("pi to 23 decimal places = 3.14159265358979323846264 " "(Wikipedia)\n"); a = 4 * atanl((real)1); // atanl always gets 'long double' printf("pi to 23 decimal places = %25.23Lf\n(computed by ", a); #ifdef GNUC printf("gnu GCC Compiler with '-std=c99' option)\n"); #elif defined( DMC ) printf("digital Mars C Compiler)\n"); #else printf("another compiler)\n"); #endif printf("'double' - pi = %Lg, 'long double' - pi = %Lg\n", b1 - a, b2 - a); return b1!= b2? 0 : 1; 3
Modul vytvořený překladačem Digital Mars (je dostupný z Code::Blocks) poskytne výsledek: length of 'long double' = 10, epsilon = 1.0842e-19 pi to 23 decimal places = 3.14159265358979323846264 (Wikipedia) pi to 23 decimal places = 3.14159265358979323850000 (computed by Digital Mars C Compiler) 'double' - pi = -1.22515e-16, 'long double' - pi = -2.1684e-19 Povšimněme, že proměnná long double je umístěna v deseti bytech. Z výsledné hodnoty epsilon je vidět, že lze počítat s přesností zhruba na devatenáct až dvacet platných cifer. Z výsledku na třetím řádku je dále vidět, že překladač Digital Mars dbá o to, aby se nevypisovala desetinná místa, která z principu nemohou být správně (požadovaná zbylá místa jsou doplněna nulami). Modul vytvořený překladačem GNU GCC (standardní v Code::Blocks) poskytne výsledek: length of 'long double' = 12, epsilon = 1.0842e-019 pi to 23 decimal places = 3.14159265358979323846264 (Wikipedia) pi to 23 decimal places = 3.14159265358979323851281 (computed by GNU GCC Compiler with '-std=c99' option) 'double' - pi = 0, 'long double' - pi = 0 Povšimněme, že proměnná long double je tentokrát umístěna v dvanácti bytech, což je dáno organizací paměti překladače GNU GCC. Mantisa a tedy přesnost výpočtu je ovšem stejná, o čemž svědčí vypočtená hodnota epsilon. Dále je zjevné, že překladač GNU GCC vypisuje i bity na konci, které z principu nemusí být správné (tj. více než dvacet platných cifer.) 4
2 Rysy převzaté z C++ Některé z nových rysů standardu C99 byly převzaty z C++. Týká se to inline funkcí, komentářů uvedených //, rozptýlené příkazy a deklarace a proměnná viditelná pouze v rozsahu cyklu. 2.1 Funkce inline Nové klíčové slovo inline indikuje, že funkce by měla být rozvinuta inline, je-li to možné (záleží to na překladači; při rozvinutí inline nedochází k předávání parametrů a běh úlohy by tak měl být rychlejší). Moderní překladače však dokáží velmi dobře optimalizovat kód a deklarace inline tak postupně pozbývá svůj původní význam. Klasickým příkladem užití je alokování paměti: #include <sys/types.h> #include <malloc.h> inline void *allocate(size_t sz) { return malloc(sz); 2.2 Komentáře uvedené // Vedle tradičního užití komentářů /*... */ lze používat // jako v C++, přičemž konec řádku za // je chápán jako komentář. Většina překladačů jazyka C používala tento rys již řadu let, nyní se toto ovšem už stalo závaznou normou. Prostředí jako je Code::Blocks používají různé barvy k vytvoření hierarchie komentářů, např. 5
#include <iostream> // Digital Mars C allows "#include <iostream.h>", too using namespace std; /** * This is a documentation comment block * @param xxx does this (this is the documentation keyword) * @authr some user (this is the documentation keyword error) */ int main(int argc, char **argv) { /// This is a documentation comment line int numbers[20], total = 0; double average; char ch = '\n'; for (int i = 0; i < 20; ++i) // a breakpoint is set { numbers[i] = i; total += i; average = total / 20.; std::cout << numbers[0] << ch << "..." << ch << numbers[19] << ch; /// cout without std:: is only allowed with 'using namespace std;' cout << "total: " << total << ", average: " << average << ch; std::cout << "put any character and press Enter... "; std::cin >> ch; std::cout << ch << " has been entered" << '\n'; 6
2.3 Rozptýlené příkazy a deklarace V starších verzích jazyka C se deklarace mohly vyskytovat pouze na začátku bloku před všemi příkazy. V standardu C99 lze deklarace umísťovat do libovolného místa kódu stejně jako je tomu v C++ a jako je tomu např. v následujícím fragmentu kódu splňujícím standard C99: typedef struct { int age; double salary; Employee; void compensations(employee *emp) { double bonus = 155.25; double salary; salary = emp->salary + bonus; int rank; // povoleno pouze v C99 rank = emp->age - 25; //... zbytek kódu 2.4 Proměnné viditelné v rozsahu cyklu Fragment kódu (i tento kód je v Code::Blocks možné přeložit pouze s option -std=c99) for (int n = 0; n < 10; ++n) { // příkazy 7
je ekvivalentní klasickému kódu { int n = 0; for (; n < 10; ++n) { // příkazy 8
3 Originální rysy v C99 Novými rysy C99 jsou pole s proměnnou délkou, přiřazené inicializátory, datový typ long long, variadická makra a identifikátor func. 3.1 Pole s proměnnou délkou jde o nejvýznamnější nový rys C99: int sentvla(int [*], int ); int createvla(int size, int *sumquad) { int arr[size]; // size isn't const; allowed only in C99 for (int i = 0; i < size; i++) arr[i] = i * i; *sumquad = sentvla(arr, size); return sizeof(arr); #if defined( GNUC ) int sentvla(int vla[], int size) { // GNU C allows "[*]" in prototypes only #elif defined( DMC ) int sentvla(int vla[*], int size) { // Digital Mars C also allows "[*]" here #else int sentvla(int *vla, int size) { // another possible way of compilation #endif int suma = 0; for (int i = 0; i < size; i++) suma += vla[i]; return suma; 9
Ve všech předchozích verzích jazyka C nebylo možné, aby délka pole (size) byla proměnná, tj. známá až v okamžiku běhu programu (nikoliv v okamžiku kompilace, jak tomu bylo dříve) ve výše uvedeném příkladu délka dynamicky alokovaného pole v proceduře vstupuje jako parametr procedury. Tato možnost neexistuje ani v C++ zde je nutné pole dynamicky alokovat pomocí operátorů new a delete[], což rozhodně není tak elegantní jako v C99. K podpoře polí s proměnnou délkou C99 zavádí notaci [*] v parametrech funkce ovšem některé překladače toto umožňují jen v deklaraci prototypu funkce (jako GNU GCC) a jiné i v konkrétní definici funkce (jako Digital Mars). Běžné debuggery mají potíže se zobrazováním obsahu dynamicky alokovaných polí (např. klasické verze WinDbg), nové verze debuggeru GDB pro GNU GCC však zobrazují dynamicky alokovaná pole správně. Rovněž vícerozměrná pole lze v standardu C99 alokovat dynamicky. Výše uvedené funkce lze např. použít v následujícím testovacím hlavním programu: #include <stdio.h> #include "variablesize_func.c" void main() { int size, nbytes, sumquad; printf("enter array's size: "); scanf("%d", &size); nbytes = createvla(size, &sumquad); printf("nbytes = %d, sumquad = %d\n", nbytes, sumquad); 10
3.2 Přiřazené inicializátory Přiřazené inicializátory umožňují elegantním způsobem přiřazovat počáteční hodnoty prvkům polí, struktur a unionů podle následujícího příkladu: #include <stdio.h> int vec[5] = {[1]=10, [3]=20; typedef struct { char name[20]; int ID; int age; FILE *record; Employee; Employee emp = {.ID=0,.record=NULL; 3.3 Datový typ long long Tento datový typ musí podle standardu mít nejméně 64 bitů (důležité např. pro náhodná čísla) #include <stdio.h> void main() { long long molecules=10000000000000ll; printf("%lld\n", molecules); 11
3.4 Variadická makra Standard C99 podporuje kromě variadických funkcí (printf funkce s proměnným počtem parametrů) i variadická makra, přičemž proměnný seznam argumentů makra reprezentuje ellipsis (... ): #include <stdio.h> #define DoLogFile(...) fprintf(stderr, VA_ARGS ) #define MakeName(...) # VA_ARGS int main() { char *p; p = MakeName(Mary); p = MakeName(Doe, John); DoLogFile("MakeName=%s\n", p); // kombinace dvou variadických maker: DoLogFile("MakeName=%s\n", MakeName(Martin Luther King, Jr.)); 12
3.5 Identifikátor func Identifikátor func vrací jméno funkce, v které byl tento identifikátor použit. Jméno funkce je tedy poskytováno podobným způsobem jako pracují např. makra FILE nebo DATE : #include <stdio.h> void moje_funkce() { printf("you're in %s\n", func ); void main() { moje_funkce(); 3.6 Komplexní čísla Standard C99 přináší nová klíčová slova _Complex a _Imaginary a je přípustná deklarace long double _Complex a (přirozená) práce s komplexními čísly s přesností na 19 až 20 platných cifer je tedy možná. Na následující straně je uveden příklad kódu použitelného pro překladače Digital Mars C i GNU GCC. 13
#include <stdio.h> //nebyl použit complex.h, aby bylo jasné co je generické inline long double creall(long double _Complex z) { return (long double)z; //nebo *(long double *)&z, reálná část je první inline long double cimagl(long double _Complex z) { return ((long double *)&z)[1]; //realná a imaginární jsou tedy za sebou long double _Complex csinl(long double _Complex); //csinl je v knihovnách #ifdef DMC //pozor, I je definováno jiným typem v Digital Mars a GNU C! #define I ((const float _Imaginary) imaginary) //DMC má typ _Imaginary #elif defined( GNUC ) //DMC tedy definuje I jako typ standardu _Imaginary #define I (0.0F + 1.0iF) //v MinGW, GCC totiž NEMÁ dosud typ _Imaginary #endif //oba nejprve definují standard _Complex_I a oba rovn ěž definují i I /* GCC ovšem umožňuje psát i za číslem, tj. např. a = 1.3L+2.5iL */ void main() { long double _Complex a = 1.3L+2.5L*I, b = -2.7L-0.3L*I, sum, mul, foo, t; printf("a = %Lg + %Lgi (size(a)=%d)\n", creall(a), cimagl(a), sizeof(a)); printf("b = %Lg + %Lgi\n", creall(b), cimagl(b)); sum = a + b; mul = a * b; printf("a + b = %Lg + %Lgi\n", creall(sum), cimagl(sum)); printf("a * b = %Lg + %Lgi\n", creall(mul), cimagl(mul)); foo = csinl(a); t = csinl(1.5707963267948966192l + 1.3169578969248167086L*I); printf("sin(a) = %.20Lg + %.20Lgi\nsin(pi/2 + ln(2+sqrt(3))i) = " "%.20Lg + %.20Lgi", creall(foo), cimagl(foo), creall(t), cimagl(t)); 14
Program vytvořený překladačem Digital Mars poskytne výsledek (je vidět, že jedno komplexní číslo zabírá v tomto případě 20 bytů): a = 1.3 + 2.5i (size(a)=20) b = -2.7 + -0.3i a + b = -1.4 + 2.2i a * b = -2.76 + -7.14i sin(a) = 5.9088177234776839696 + 1.618422611617372858i sin(pi/2 + ln(2+sqrt(3))i) = 2 + 1.4084199363160870024e-19i a program vytvořený překladačem GNU GCC poskytne obdobný výsledek (v tomto případě ovšem komplexní číslo zabere 24 bytů): a = 1.3 + 2.5i (size(a)=24) b = -2.7 + -0.3i a + b = -1.4 + 2.2i a * b = -2.76 + -7.14i sin(a) = 5.9088177234776838326 + 1.6184226116173728588i sin(pi/2 + ln(2+sqrt(3))i) = 2 + 1.4434466152095499672e-019i 3.7 Použití knihovny s čtyřnásobnou délkou slova Ještě mnohem větší přesnosti dosáhneme při použití nově vytvořené knihovny libquadmath pro překladač GNU GCC (tuto knihovnu musíme zatím při procesu sestavování ručně připojovat, což ovšem není velký problém). Způsob použití je zjevný z následujícího kódu, který je přesnější variantou dříve demonstrovaného testu přesnosti. 15
#include <stdio.h> #include <quadmath.h> typedef float128 real; int main() { real a = 1, b1 = 3.14159265358979323846264338327950288, //bude v GCC long double b2 = 3.14159265358979323846264338327950288Q; //bude v GCC float128 // epsilon computing while (1 + a/2!= 1) a /= 2; printf("length of ' float128' = %d, epsilon = %.4g\n", sizeof(a), (double)a); // pi computing printf("pi to 35 decimal digits = 3.14159265358979323846264338327950288 " "(Wikipedia)\n"); a = 4 * atanq((real)1); //atanq má parametr typu float128 char s[40]; quadmath_snprintf (s, sizeof(s), "%39.36Qf", a); //speciální tisk v quad printf("pi to 36 decimal digits =%s (computed\nby ", s); #ifdef GNUC printf("gnu GCC Compiler with '-std=c99' option and " "libquadmath.a library)\n"); #else printf("another compiler)\n"); #endif printf("'long double' - pi = %.4g, ' float128' - pi = %.4g", (double)(b1 - a), (double)(b2 - a)); return b1!= b2? 0 : 1; 16
a v tomto případě můžeme počítat s přesností výpočtu až na 35 platných cifer (viz epsilon): length of ' float128' = 16, epsilon = 1.926e-034 pi to 35 decimal digits = 3.14159265358979323846264338327950288 (Wikipedia) pi to 36 decimal digits = 3.141592653589793238462643383279502797 (computed by GNU GCC Compiler with '-std=c99' option and libquadmath.a library) 'long double' - pi = 5.017e-020, ' float128' - pi = 0 Knihovna libquadmath umožňuje ve stejné přesnosti pracovat i s komplexními čísly: #include <quadmath.h> #include <stdio.h> int main() { complex128 t; t = csinq(1.570796326794896619231321691639751399q + 1.316957896924816708625046347307968481Qi); printf("sin(pi/2 + ln(2+sqrt(3))i) = %.20Lg + %Lgi", (long double)crealq(t), (long double)cimagq(t)); return 0; což poskytne výsledek sin(pi/2 + ln(2+sqrt(3))i) = 2 + 7.51001e-035i který opět potvrzuje přesnost přibližně na 34 platných cifer. 17
4 Náročný test přesnosti Účelnost aritmetiky v čtyřnásobné přesnosti lze názorně dokumentovat následujícím příkladem v C: #include <quadmath.h> #include <stdio.h> int main (void) { float128 a = sinq(1e22q); float128 b = logq(17.1q); float128 c = expq(0.42q); float128 d = 173746*a + 94228*b - 78487*c; char s[45]; quadmath_snprintf(s, sizeof(s), "%44.35Qe", d); // bez Q nefunguje printf("result to 36 decimal digits: %s\n", s); printf("non-precise standard printing:%44.35e\n", (double)d); printf("interesting precise printing: %44.35Le", (long double)d); // Le je v c99! nebo v Fortran90: program cise implicit none real(16) a, b, c, d a = sin(1e22_16) b = log(17.1_16) c = exp(0.42_16) d = 173746*a + 94228*b - 78487*c print *, d end program cise Oba programy dávají (správný výsledek) -1.34181895782961954211800625066264897e-012. Pokud bychom to provedli v long double, výsledek by byl -1.3145040611561853e-012 a v double dokonce úplně špatný výsledek 2.9103830456733704e-011. 18
Obsah 1 Standardizace prvků jazyka ne dříve vždy povinných 2 2 Rysy převzaté z C++ 5 2.1 Funkce inline........................................ 5 2.2 Komentáře uvedené //.................................... 5 2.3 Rozptýlené příkazy a deklarace............................... 7 2.4 Proměnné viditelné v rozsahu cyklu............................ 7 3 Originální rysy v C99 9 3.1 Pole s proměnnou délkou jde o nejvýznamnější nový rys C99:............. 9 3.2 Přiřazené inicializátory................................... 11 3.3 Datový typ long long.................................... 11 3.4 Variadická makra...................................... 12 3.5 Identifikátor func................................... 13 3.6 Komplexní čísla....................................... 13 3.7 Použití knihovny s čtyřnásobnou délkou slova....................... 15 4 Náročný test přesnosti 18 19