Programmiersprachen (Re: [linux-l] Ruby: sehr cool, aber laaaahm... wie geht's schneller?! - D?)

Axel Weiß aweiss at informatik.hu-berlin.de
So Aug 27 17:11:59 CEST 2006


Oliver Bandel wrote:
> Könnten ja zwei Programmierer sein, die je eine Implementation
> bringen, also gibt man das Interface vor.
> Der bessere Code wird dann später fest einkompiliert.
> <Ketzer>
>    (Der schlechtere für die kostenlose Demo-Version? ;-))
> </Ketzer>

Das geht in C aber einfacher (und besser strukturiert). Ein Dritter(TM) habe 
mal die Ableitung als echte Grenzwertberechnung mit (statisch) vorgegebener 
Genauigkeit des Resultats programmiert, DIE wollen wir bei Bedarf verwenden 
(nachdem ER seine Änderungen eingecheckt hat).

Dazu wird die Ableitungsfunktion in ein Modul gesteckt. Interface (derive.h):

#ifndef _derive_h_
#define _derive_h_
double derive(double (*)(double), double);
#endif

Die Implementierung (derive.c) erfolgt in einer separaten Datei:

#include <math.h>
#ifdef USE_STATIC_EPSILON

#ifndef EPSILON
#define EPSILON 0.001
#endif
double derive(double (*f)(double), double x){
	double d = f(x + EPSILON / 2) - f(x - EPSILON / 2);
	return d / EPSILON;
}

#else /* USE_STATIC_EPSILON */

#ifndef GENAUIGKEIT
#define GENAUIGKEIT 0.000001
#endif
double derive(double (*f)(double), double x){
	double e = 1.0, d0, d = GENAUIGKEIT;
	do {
		d0 = d, e /= 2;
		d = (f(x + e) - f(x - e)) / 2 / e;
	} while (fabs(d - d0) > GENAUIGKEIT);
	return d;
}

#endif

Dabei wird das Symbol USE_STATIC_EPSILON ausgewertet, und falls es definiert 
ist, die 'alte' Ableitungsfunktion genommen. Falls nicht, dann eben die neue. 
In beiden Verfahren wird eine Maßzahl benötigt, die mit einem Standardwert 
belegt wird, falls sie nicht definiert ist. (NB: wie geht das in OCaml?)

Die Verwendung (test_derive.c) geht dann (fast) wie gehabt (hab' da noch ein 
paar Makros eingeführt ;)

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

#define BERECHNUNGEN 64
#define SCHRITT 0.1
#define F(name, result) double name(double x){return result;}

F(square, x * x)
F(cube, x * x * x)

static const struct {
	double (*f)(double);
	const char *name;
} function_table[] = {
	{sin,    "Sinus"},
	{cos,    "Kosinus"},
	{square, "Quadrat (Parabel)"},
	{cube,   "Kubik"},
	/* hier weitere Funktionen reinstecken */
};

int main(){
	int i, j;
	for (i=0; i<sizeof(function_table)/sizeof(function_table[0]); ++i){
		printf("\n\nBerechnung von %s:\n", function_table[i].name);
		for (j=0; j<BERECHNUNGEN; ++j){
			double x = SCHRITT * j;
			double y = function_table[i].f(x);
			double dy = derive(function_table[i].f, x);
			printf("x: %f, y: %f, y': %f\n", x, y, dy);
		}
	}
	return 0;
}

Der Trick ist jetzt, dass im Makefile gesagt wird, was passiert. Hier werden 
die Compilerschalter gestellt, also z.B.

TARGET := test_derive
CFLAGS += -Wall -O2
CFLAGS += -DGENAUIGKEIT=0.00001
LDFLAGS += -lm

all: $(TARGET)
$(TARGET): derive.o test_derive.c
	$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
derive.o: derive.c derive.h Makefile
	$(CC) -o $@ -c $< $(CFLAGS)

Alternativ könnte ich auch etwa schreiben:
CFLAGS += -DUSE_STATIC_EPSILON -DEPSILON=0.01

Oliver, ich glaub' Du hast noch nicht das richtige Beispiel gefunden, um zu 
zeigen wo OCaml besonders gut ist. Lass Dir mal ein Beispiel mit Listen oder 
Bäumen einfallen, da ist C immer ziemlich umständlich.

Ach ja:
> (Und typsicher ist es in C auch nicht.)

Falsch. Hier ist jedenfalls alles sauber und typsicher!

Gruß,
			Axel





Mehr Informationen über die Mailingliste linux-l