Tag: "mathematik"

Das gewichtete Mittel

Posted on Sep 20, 2005 von in SQL Server

Angenommen, wir haben folgendes Portfolio:

Anlageklasse Anteil am Portfolio
Aktien 70%
Renten 20%
Cash 10%

Für Aktien erwarten wir einen Return von 3%, für Renten 5% (jaja, wir haben "High-Yield" Bonds :-) ), und Cash 1,5%. Was ist nun der durchschnittliche Return über das gesamte Portfolio?

Per Hand würde man (0,03 * 0,7)+(0,05 * 0,2) + (0,015 * 0,1) = 0,0325 = 3,25% errechnen.

In Excel würde man einfach alles in drei Spalten untereinander schreiben und dann per SUMMENPRODUKT ein identisches Ergebnis ausrechnen und mit T-SQL?

CREATE TABLE #t
(expected_return FLOAT NOT NULL
, weight FLOAT NOT NULL
)
INSERT INTO #t SELECT 0.03, 0.7
UNION ALL
SELECT 0.05, 0.2
UNION ALL
SELECT 0.015, 0.1

SELECT SUM(expected_return*weight)
FROM #t

DROP TABLE #t

-----------------------------------------------------
3.2500000000000001E-2

(1 row(s) affected)

Present und Future Value einer Einmalanlage

Posted on Mai 6, 2005 von in SQL Server

Der Spatz in der Hand ist mehr wert als die Taube auf dem Dach. So ähnlich lautet ein beliebtes Sprichwort. Übertragen auf Geld würde man wahrscheinlich sagen, daß 1 Euro in der Hand heute mehr wert ist als 1 Euro, den man irgendwann in der Zukunft erhält. Warum? Nun, den Euro, den ich heute habe, kann ich investieren, erhalte dafür z.B. Zinsen und habe so schließlich mehr Geld als diesen Euro in der Zukunft.

Den Vorgang von Present zu Future Value nennt man "Compounding". Damit ist ein arithmetischer Prozess definiert, der den Endwert eines Cash Flows oder einer Serie von Cash Flows feststellt, wenn man "Compounded Interest" unterstellt. Auf Deutsch würde man hier Zinseszinsrechnung sagen.

So, genug der Einleitung. Skizzieren wir einmal ein Beispielszenario zu unserer Fragestellung:
Angenommen, ich habe 1.000€ und kann diese zu einem Zinssatz von 6,00% investieren. Wieviel Geld habe ich nach einem Jahr?

Formelmäßig könnte man das folgendermaßen notieren:
FV=PV+PV*i
=> FV=PV*(1+i)^1
=> 1.000*(1+0,06)^1
=> 1.060

Dies ist einfach nachzuvollziehen und auch einfach in T-SQL nachzubauen:

DECLARE @pv FLOAT
DECLARE @i FLOAT
DECLARE @n FLOAT
SELECT
@pv=1000, @i=0.06, @n=1
SELECT
ROUND(@pv*POWER(1+@i, @n),2)

-----------------------------------------------------
1060.0

(1 row(s) affected)

Hier an diesem Beispiel wird bereits eine weitere "Besonderheit" deutlich. Ich verwende idR FLOAT Daten und Datentypen. Ich habe hier die Erfahrung gemacht, daß FLOAT gerade bei komplexeren Berechnungen "genauer" rechnet als z.B. DECIMAL. Zumindest ist der Grad der "Abweichung vom exakten Ergebnis" für mich und die Zwecke dieser Berechnungen akzeptabel. Ich möchte aber darauf hinweisen, daß der geneigte Leser sich sein eigenes Urteil bilden sollte und, abhängig davon, wofür er diese Berechnungen einsetzt, beide Alternativen (FLOAT und DECIMAL) anhand seiner eigenen Daten durchspielen sollte. Ich vermute, die Antwort darauf, was der "besser" geeignete Datentyp ist, lautet: "Das hängt davon ab...".

So, zurück zu unserer Einführung. Wie wir gesehen haben, werden nach einem Jahr aus 1.000€ bei 6,00% Zins 1.060€. Angenommen ich lasse diese 1.000 aber nun 5 Jahre zu diesem Zinssatz angelegt und kann gleichzeitig die erhaltenen Zinsen ebenfalls zu diesem Zinssatz investieren. Wieviel Geld habe ich nach 5 Jahren?

FV=PV*(1+i)(1+i)(1+i)(1+i)(1+i)
=> PV*(1+i)^5
=> 1.000*(1+0.06)^5
=> 1.338,23

Wenn man sich jetzt die T-SQL Implementation ansieht, erkennt man, daß man eigentlich "nur" die Variable @n auf den neuen Wert anpassen muß:

DECLARE @pv FLOAT
DECLARE @i FLOAT
DECLARE @n FLOAT
SELECT
@pv=1000, @i=0.06, @n=5
SELECT
ROUND(@pv*POWER(1+@i, @n),2)

-----------------------------------------------------
1338.23

(1 row(s) affected)

Einfach.

Bis jetzt sind wir aber davon ausgegangen, daß unser Investment einmal im Jahr Zinsen ausschüttet. Gerade aber im amerikanischen Finanzraum ist eine häufigere Ausschüttung (halb- oder sogar vierteljährlich) üblich.

Unterstellen wir, daß in unserem 5 Jahres Beispiel, die Zinsschüttung halbjährlich erfolgt. Was müssen wir jetzt berücksichtigen?
Als Erstes rechnen wir den jährlichen Zinssatz in einen "periodengerechten" um. Also 6.00% / 2 = 3,00%.
Danach rechnen wir die Laufzeit in Jahren in Laufzeit pro Zinsperiode um. Also: 5 (Jahre) * 2 (x jährlich Zinsausschüttung) = 10 Zinsperioden.

So, in einer Formel ausgedrückt, hat sich damit nichts verändert.

DECLARE @pv FLOAT
DECLARE @i FLOAT
DECLARE @n FLOAT
SELECT
@pv=1000, @i=0.03, @n=10
SELECT
ROUND(@pv*POWER(1+@i, @n),2)

-----------------------------------------------------
1343.9200000000001

(1 row(s) affected)

Jetzt kann man die Umrechnung in periodengerechte Werte vorher vornehmen. Oder, was vielleicht geschickter und generischer ist, die Formel umstellen, indem einfach ein weiterer Parameter @m eingebaut wird. @m nimmt die Anzahl der Zinszahlungen pro Jahr auf. Damit sieht unsere Formel jetzt folgendermaßen aus:

DECLARE @pv FLOAT
DECLARE @i FLOAT
DECLARE @m FLOAT
DECLARE @n FLOAT
SELECT
@pv=1000, @i=0.06, @m=2, @n=5
SELECT
ROUND(@pv*POWER(1+@i/@m,@m*@n),2)

-----------------------------------------------------
1343.9200000000001

(1 row(s) affected)

Offensichtlich wird aus 1.000€ bei halbjährlicher Zinszahlung mehr als bei jährlicher Zinszahlung. Dies ist Schulmathematik und bedarf keiner weiteren Erklärung hier. Interessant aber ist die Frage nach der Effektivverzinsung meines Investments. Diese läßt sich durch die folgende Formel ermitteln:

DECLARE @i FLOAT
DECLARE @m FLOAT
SELECT
@i=0.06, @m=2
SELECT
(POWER(1+@i/@m,@m)-1)*100

-----------------------------------------------------
6.0899999999999954

(1 row(s) affected)

Die Multiplikation *100 ist nicht zwingend notwendig. Ich habe sie hier nur eingebaut, um einen direkten Vergleich mit Excel's EFFEKTIV() Funktion zu erleichtern. Im Grunde ist dies nicht anderes als die T-SQL Adaption dieser Excel Funktion. Vorsicht! An dieser Stelle ein ROUND() einzubauen, wäre nicht sehr klug. Gerade wenn man auf die Multiplikation mit 100 verzichten will:

DECLARE @i FLOAT
DECLARE @m FLOAT
SELECT
@i=0.06, @m=2
SELECT
ROUND((POWER(1+@i/@m,@m)-1),2)

-----------------------------------------------------
5.9999999999999998E-2

(1 row(s) affected)

Was nichts anderes ist als der ursprüngliche Eingangszinssatz.

Die bisherigen Berechnungen haben stets einen Future Value aus einem Present Value errechnet. Natürlich funktioniert dies auch in entgegengesetzter Richtung. Dann lauten die Fragestellung eher so:

"Wieviel muß ich heute investieren, um in Zukunft eine Summe x zu haben?"

Bezogen auf unser erstes Beispiel oben haben wir 1.060€ Euro nach einem Jahr. Wieviel muß ich also heute investieren, um diese Summe bei einem Zinssatz von 6,00% nach einem Jahr zu haben?

Formelmäßig könnte man das folgendermaßen notieren:
PV=FV/(1+i)^1
=> PV=FV*(1/1+i)^n
=> 1.060*(1/1+0,06)^1
=> 1.000

Hier ist die entsprechende T-SQL Formel:

DECLARE @fv FLOAT
DECLARE @i FLOAT
DECLARE @n FLOAT
SELECT
@fv=1060, @i=0.06, @n=1
SELECT
ROUND(@fv*POWER(1+@i,-@n),2)

-----------------------------------------------------
1000.0

(1 row(s) affected)

Wie man hier bereits sieht, läßt sich diese Formel auch unverändert zur Berechnung anderer Laufzeiten verwenden.

DECLARE @fv FLOAT
DECLARE @i FLOAT
DECLARE @n FLOAT
SELECT
@fv=1338.23, @i=0.06, @n=5
SELECT
ROUND(@fv*POWER(1+@i,-@n),2)

-----------------------------------------------------
1000.0

(1 row(s) affected)

Mathematisch ist es äquivalent, ob ich die n-te Wurzel ziehe oder ^1/n rechne.

Und selbstverständlich kann man auch den Present Value bei häufigerer Anzahl der Zinszahlungen pro Jahr ermitteln.

DECLARE @fv FLOAT
DECLARE @i FLOAT
DECLARE @m FLOAT
DECLARE @n FLOAT
SELECT
@fv=1343.92, @i=0.06, @m=2, @n=5
SELECT
ROUND(@fv*POWER(1+@i/@m,-@m*@n),2)

-----------------------------------------------------
1000.0

(1 row(s) affected)

So, jetzt können wir Present und Future Values hin und zurück berechnen
Zum Abschluß dieser kleinen Einführung wollen wir uns noch einer in der Praxis recht häufig auftretenden Fragestellung widmen. Der leichteren Nachvollziehbarkeit bleiben wir bei unserem Beispiel und den bereits bekannten Ergebnissen.

Angenommen, wir wollen wissen, nach welcher Zeit aus 1.000€ 1.343,92€ werden bei einer jährlichen Verzinsung von 6,00%.

Mathematisch lautet diese Formel:
n= (ln(FV)-ln(PV)/ln(1+i))

DECLARE @pv FLOAT
DECLARE @fv FLOAT
DECLARE @i FLOAT
SELECT
@pv=1000, @fv=1338.23, @i=0.06
SELECT
ROUND((LOG(@fv)-LOG(@pv))/LOG(1+@i),2)

-----------------------------------------------------
5.0

(1 row(s) affected)

Die wahrscheinlich noch interessantere Frage nach der Verzinsung eines Papieres, das ich heute zu 1.000 kaufe und welches in 5 Jahren zu 1.338,23 zurückgezahlt wird, stellen wir hier zurück. Sie ist nicht ganz so trivial zu lösen und wird im einem eigenen Beitrag betrachtet. Diese Frage fällt auch insofern hier aus dem Rahmen, als das die hier gezeigten Formeln ohne weiteres in setbasierten SELECTs eingebaut werden können, während die Frage nach der Vezinsung hingegen einen iterativen Trial- and-Error Prozeß beschreibt.

So, Ende dieser kurzen Einführung in das Thema dieser Kategorie. Mathematik ist nicht jedermanns Sache aber vielleicht hat der eine oder andere Leser ja doch mal Verwendung für die hier beschriebenen Verfahren. :-)

Summenwert einer Reihe

Posted on Aug 18, 2004 von in SQL Server

Dies ist ein beliebtes Beispiel für Informatikstudenten im Anfangsstadium, um die Auswirkungen effizienter Algorithmen zu demonstrieren. Also auch hier nicht unbedingt etwas, was man zwingend in einer Datenbank machen müßte, das sich aber durchaus mengenbasiert lösen läßt. Zu diesem Algorithmus gibt es eine kleinen Anekdote:

Dem "Entdecker" Carl-Friedrich Gauß wurde in der Schule die Aufgabe gestellt, die Summe aller Zahlen von 1 bis 100 zu berechnen. Alle Kinder rechneten los, Gauß schrieb kurz etwas auf seine Tafel und riss nach kurzer Zeit seinen Lehrer aus dessen Ruhepause. Die Formel stimmte natürlich! Wer es genauer nachlesen möchte, kann sich mal hier umschauen.

DECLARE @n BIGINT
SET @n = 100
SELECT (@n+@n*@n)/2
                     
-------------------- 
5050

(1 row(s) affected)

oder als UDF

CREATE FUNCTION dbo.achtsieben(@n BIGINT) 
RETURNS BIGINT
	AS
		BEGIN
    	RETURN (@n+@n*@n)/2
		END
GO
SELECT dbo.achtsieben(100)
DROP FUNCTION dbo.achtsieben
                     
-------------------- 
5050

(1 row(s) affected)

Zu dem Namen, den ich der Funktion gegeben habe, gibt es auch eine Anekdote:

Als ich unsere Mathematiker nach der "richtigen" Bezeichnung für diese Formel gefragt habe, kam als Antwort:

"Wir nennen das die 78-er Regel, da die Summe der Monate eines Jahres 78 ist."

Ein kurzer Test ergibt:

DECLARE @n BIGINT
SET @n = 12
SELECT (@n+@n*@n)/2
                     
-------------------- 
78

(1 row(s) affected)

Stimmt!

Nachtrag 27.08.2004: Auch im SQL Server kann man damit die Notwendigkeit zum Einsatz von effizienten Algorithmen demonstrieren. Dazu bauen wir uns mal folgendes Testskript zusammen:

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
GO
DECLARE @start DATETIME
SET @start = GETDATE()
DECLARE @n BIGINT 
SET @n = 200000 
SELECT (@n+@n*@n)/2
SELECT GETDATE()-@start AS Zeit
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
GO
DECLARE @start DATETIME
DECLARE @n BIGINT 
DECLARE @result BIGINT 
SET @start = GETDATE()
SET @n = 1
SET @result = 0
WHILE @n <= 200000
	BEGIN
		SET @result = @result + @n
		SET @n = @n + 1
	END
SELECT @result
SELECT GETDATE()-@start AS Zeit

Nach Ausführung erhält man folgendes Ergebnis:

...
                     
-------------------- 
20000100000

(1 row(s) affected)

Zeit                                                   
------------------------------------------------------ 
1900-01-01 00:00:00.010

(1 row(s) affected)

...
                     
-------------------- 
20000100000

(1 row(s) affected)

Zeit                                                   
------------------------------------------------------ 
1900-01-01 00:00:01.513

(1 row(s) affected)

Während der Gauß Algorithmus fast augenblicklich das Ergebnis zurückliefern, braucht die iterative Methode deutlich länger!

Vielleicht mag das nicht viel erscheinen, aber jetzt stelle ich mir die Auswirkungen auf ein System vor, in dem ein solcher algorithmus in einer stark frequentierten Prozedur implementiert wurde, die mehrere Tausend Male pro Stunde aufgerufen wird. Nun sieht die Sache schon etwas anders aus. Jetzt mag man natürlich mit Caching argumentieren, aber in diesem Fall wird die Ausführung ohne

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
GO

zwischen zwei Läufen der iterativen Methode auch nicht wirklich schneller

Zeit                                                   
------------------------------------------------------ 
1900-01-01 00:00:01.403

(1 row(s) affected)

Dies ist natürlich kein repräsentativer Test unter sterilen Testbedingungen, aber verdeutlicht doch die Notwendigkeit effizienter Algorithmen, auch, und vielleicht gerade, in T-SQL. 

N-te Wurzel einer Zahl

Posted on Aug 3, 2004 von in SQL Server

Tja, wieder so ein Beispiel, dass man mal in der Schule gelernt hat, aber immer genau dann vergisst, wenn man es braucht.

DECLARE @My1 FLOAT
DECLARE @My2 FLOAT

SELECT @My1 = 16, @My2 = 4
SELECT POWER(@My1, 1/@My2)
                                                      
----------------------------------------------------- 
2.0

(1 row(s) affected)

Oder als UDF-Version

CREATE FUNCTION nthroot(@My1 FLOAT, @My2 FLOAT) 
RETURNS FLOAT
	AS
		BEGIN
			RETURN POWER(@My1,1/@My2)
		END
GO
SELECT dbo.nthroot(16,4)
DROP FUNCTION dbo.nthroot
                                                      
----------------------------------------------------- 
2.0

(1 row(s) affected)

Kleinste gemeinsame Vielfache zweier Zahlen

Posted on Aug 3, 2004 von in SQL Server
CREATE FUNCTION dbo.kgv(@zahl1 int, @zahl2 int ) 
RETURNS INT
AS 
  BEGIN
   RETURN (@zahl1 * @zahl2) / dbo.ggt(@zahl1, @zahl2)
  END
 GO

SELECT dbo.kgv(24,36)
DROP FUNCTION dbo.kgv
            
----------- 
72

(1 row(s) affected)

Der Vollständigkeit halber hier noch einmal die Funktion zur Ermittlung des grösssten gemeinsamen Teilers:

CREATE FUNCTION dbo.ggt(@zahl1 int, @zahl2 int) 
RETURNS INT
AS
 BEGIN
   DECLARE @zahl3 INT
   SET @zahl3=1
   WHILE (@zahl3 <> 0)
    BEGIN        
     SET @zahl3=@zahl1 % @zahl2
     SET @zahl1=@zahl2
     SET @zahl2=@zahl3
    END
  RETURN @zahl1
 END
GO

Dies ist dies Adaption der Excel Funktion KGV.

Größte gemeinsame Teiler zweier Zahlen

Posted on Aug 3, 2004 von in SQL Server

Dies ist die Adaption der Excel Funktion GGT().

CREATE FUNCTION dbo.ggt(@zahl1 int, @zahl2 int) 
RETURNS INT
AS
BEGIN
DECLARE @zahl3 INT
SET @zahl3=1
WHILE (@zahl3 <> 0)
BEGIN
SET @zahl3=@zahl1 % @zahl2
SET @zahl1=@zahl2
SET @zahl2=@zahl3
END
RETURN @zahl1
END
GO
SELECT dbo.ggt(24,36)
DROP FUNCTION dbo.ggt

-----------
12

(1 row(s) affected)

Fakultät einer Zahl

Posted on Jul 26, 2004 von in SQL Server
CREATE FUNCTION dbo.fakultät(@n DECIMAL(38,0))
RETURNS DECIMAL(38,0)
AS
BEGIN
DECLARE @tmp DECIMAL(38,0)
IF (@n <= 1)
SELECT @tmp = 1
ELSE
SELECT @tmp = @n * dbo.fakultät(@n - 1)
RETURN @tmp
END
GO
SELECT dbo.fakultät(10)
DROP FUNCTION dbo.fakultät

----------------------------------------
3628800

(1 row(s) affected)

Spielt man dieses Spielchen weiter, wird man feststellen, dass man bei Zahlen grösser als 32, folgende Meldung erhält:

Server: Nachr.-Nr. 217, Schweregrad 16, Status 1,  Prozedur fakultät, Zeile 9
Die maximale Schachtelungsebene für .... (Limit ist 32).

Man wird auch feststellen, dass bei Input von 32 eine Abweichung zu Excel existiert.

Steigung einer Geraden

Posted on Jul 26, 2004 von in SQL Server
DECLARE @x1 FLOAT
DECLARE @x2 FLOAT
DECLARE @y1 FLOAT
DECLARE @y2 FLOAT

SELECT @x1 = 1, @x2 = 2, @y1 = 1, @y2 = 2
SELECT (@y2-@y1)/(@x2-@x1)

-----------------------------------------------------
1.0

(1 row(s) affected)

Oder als UDF

CREATE FUNCTION dbo.steigung(@y1 FLOAT,@y2 FLOAT, @x1 FLOAT, @x2 FLOAT)
RETURNS FLOAT
AS
BEGIN
RETURN (@y2-@y1)/(@x2-@x1)
END
GO

SELECT dbo.steigung(1,2,1,2)
DROP FUNCTION dbo.steigung


-----------------------------------------------------
1.0

(1 row(s) affected)