Gebräuchliche mathematische Funktionen implementieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie müssen sich in die Sphären der höheren Mathematik begeben, auch wenn XSLT 1.0 dies nicht tut.

Lösung

XSLT 1.0

Wir geben hier XSLT 1.0-Implementierungen für den Absolutwert, die Quadratwurzel, Logarithmen, Potenzen und Fakultäten an.

Absolutwert: ckbk:abs(x)

Der offensichtliche, aber umständliche Weg, um den Absolutwert einer Zahl zu ermitteln, wird hier gezeigt:

<xsl:template name="ckbk:abs">
  <xsl:param name="x"/>  
  <xsl:choose>
    <xsl:when test="$x &lt; 0">
      <xsl:value-of select="$x * −1"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$x"/>
    </xsl:otherwise>
  </xsl:choose>  
</xsl:template>

Der kurze, aber obskure Weg greift auf die Tatsache zurück, dass true immer in die Zahl 1 konvertiert wird false dagegen immer in die Zahl 0:

<xsl:template name="ckbk:abs">
  <xsl:param name="x"/>
  <xsl:value-of select="(1 - 2 *($x &lt; 0)) * $x"/>
</xsl:template>

Ich bevorzuge die zweite Methode, weil sie knapp und präzise ist. Alternativ können Sie eine Erweiterungsfunktion verwenden (siehe unter Code-Generierung).

Quadratwurzel: ckbk:sqrt(x)

Nate Austin steuerte die native XSLT-Funktion sqrt zu EXSLT bei, die Newtons Methode verwendet:

<xsl:template name="ckbk:sqrt">
  <!-- Die Zahl, von der Sie die Quadratwurzel ziehen möchten -->
  <xsl:param name="number" select="0"/>
  <!-- Der aktuelle 'Versuch' (try). Dies wird intern benutzt. -->
  <xsl:param name="try" select="1"/>
  <!-- Die aktuelle Iteration, geprüft gegen maxiter, um die Schleifenzahl zu begrenzen -->
  <xsl:param name="iter" select="1"/>
  <!-- Dies wird eingerichtet, um vor unendlichen Schleifen zu schützen -->
  <xsl:param name="maxiter" select="20"/>
  <!-- Dieses Template wurde von Nate Austin unter Verwendung von Sir Isaac Newtons Methode zum Ermitteln von Wurzeln geschrieben -->
  <xsl:choose>
    <xsl:when test="$try * $try = $number or $iter &gt; $maxiter">
      <xsl:value-of select="$try"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:sqrt">
        <xsl:with-param name="number" select="$number"/>
        <xsl:with-param name="try" select="$try - (($try * $try - $number) div (2 * $try))"/>
        <xsl:with-param name="iter" select="$iter + 1"/>
        <xsl:with-param name="maxiter" select="$maxiter"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Durch die Änderung des Anfangswertes von try können Sie die Leistung erheblich verbessern:

<xsl:template name="ckbk:sqrt">
  <!-- Die Zahl, von der Sie die Quadratwurzel ziehen möchten -->
  <xsl:param name="number" select="0"/>
  <!-- Der aktuelle 'Versuch' (try). Dies wird intern benutzt. -->
  <xsl:param name="try" select="($number &lt; 100) + ($number &gt;= 100 and $number &lt; 1000) * 10 + ($number &gt;= 1000 and $number &lt; 10000) * 31 + ($number &gt;= 10000) * 100"/>
  <!-- Der Rest des Codes ist gleich -->
</xsl:template>

Dieser kleine Trick (bei dem erneut die Konvertierung Boolescher in numerische Werte verwendet wird) veranlasst try, die Quadratwurzel gleich beim ersten Durchlauf besser anzunähern. Bei einem Test, in dem ich alle Wurzeln von 1 bis 10.000 habe berechnen lassen, erreichte ich eine Leistungssteigerung von 10%. Wichtiger noch, die Änderung verringerte den Durchschnittsfehler von 1 × 10−5 auf 6 × 10−13. Das bedeutet, dass Sie die Anzahl der Iterationen verringern können. Sie erhalten bei derselben Durchschnittsfehlerrate sogar noch eine bessere Leistung. Beispielsweise konnte ich die Iterationen von 10 auf 6 verringern und dabei die gleiche Fehlerrate von 10−5 erreichen, dies jedoch bei einer 50%igen Leistungssteigerung beim gleichen Test. Falls Sie Quadratwurzeln von Zahlen berechnen müssen, die viel größer sind als 10.000, sollten Sie wenigstens 10 Iterationen beibehalten oder der try-Initialisierung größere Bereiche hinzufügen.

Logarithmen: ckbk:log10(number), ckbk:log(number) und ckbk:logN(x,base)

Falls Ihr XSLT-Prozessor die EXSLT-Funktion exsl:log( ) (natürlicher Logarithmus) unterstützt, dann ist es einfach, einen Logarithmus zu einer anderen Basis zu implementieren. In Pseudocode:

<!-- Eine grundlegende Logarithmen-Regel --> 
ckbk:logN(x,base) = ckbk:logN(x,diffBase) div ckbk:logN(base, diffBase)

Leider werden auf EXSLT.org momentan keine XSLT-Prozessoren aufgeführt, die exsl:log implementieren. Ihre nächstbeste Möglichkeit besteht darin, eine Erweiterungsfunktion im Sinne der Java-Klasse java.lang.Math.log oder der JavaScript-Funktion Math.log zu implementieren. Falls Sie schließlich Erweiterungen vermeiden müssen, berechnet die folgende reine XSLT-Implementierung log10 für die meisten (vernünftigen) Anwendungen mit einer relativ guten Genauigkeit bei akzeptabler Geschwindigkeit. Sobald Sie log10( ) haben, folgt log( ) aus der Logarithmen-Regel:

<xsl:template name="ckbk:log10">
  <xsl:param name="number" select="1"/>  
  <xsl:param name="n" select="0"/>
  <!-- Merken des ganzen Teils von Ergebnis -->
  <xsl:choose>
    <xsl:when test="$number &lt;= 0">
      <!-- Logarithmen sind für 0 und negative Zahlen undefiniert. -->
      <xsl:value-of select="'NaN'"/>
    </xsl:when>
    <xsl:when test="$number &lt; 1">
      <!-- Gebrochene Zahlen haben  negative Logarithmen -->
      <xsl:call-template name="ckbk:log10">
        <xsl:with-param name="number" select="$number * 10"/>
        <xsl:with-param name="n" select="$n - 1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$number &gt; 10">
      <!-- Zahlen größer als 10 haben Logarithmen größer als 1 -->
      <xsl:call-template name="ckbk:log10">
        <xsl:with-param name="number" select="$number div 10"/>
        <xsl:with-param name="n" select="$n + 1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$number = 10">
      <xsl:value-of select="$n + 1"/>
    </xsl:when>
    <xsl:otherwise>
      <!-- Wir müssen die Berechnung nur für Zahlen im Bereich [1,10) kennen -->
      <xsl:call-template name="ckbk:log10-util">
        <xsl:with-param name="number" select="$number"/>
        <xsl:with-param name="n" select="$n"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
<!-- Berechnet den Logarithmus (natürlich) von number-->
<xsl:template name="ckbk:log">
  <xsl:param name="number" select="1"/> 
  <xsl:variable name="log10-e" select="0.4342944819"/>
  <xsl:variable name="log10">
    <xsl:call-template name="ckbk:log10">
      <xsl:with-param name="number" select="$number"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:value-of select="$log10 div $log10-e"/>
</xsl:template>
<!-- Berechnet den Logarithmus zur Basis b von number-->
<xsl:template name="ckbk:log-b">
  <xsl:param name="number" select="1"/>
  <xsl:param name="base" select="2"/>   
  <xsl:variable name="log10-base">
    <xsl:call-template name="ckbk:log10">
      <xsl:with-param name="number" select="$base"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:variable name="log10">
    <xsl:call-template name="ckbk:log10">
      <xsl:with-param name="number" select="$number"/>
    </xsl:call-template>
  </xsl:variable> 
  <xsl:value-of select="$log10 div $log10-base"/>
</xsl:template>
<!-- Berechnet log10 von Zahlen im Bereich [1,10) und liefert das Ergebnis + n-->
<xsl:template name="ckbk:log10-util">
  <xsl:param name="number"/>
  <xsl:param name="n"/>
  <xsl:param name="frac" select="0"/>
  <!-- Merken der Variablen für gebrochenen Teil -->
  <xsl:param name="k" select="0"/>
  <!-- Iterationszähler -->
  <xsl:param name="divisor" select="2"/>
  <!-- Aufeinander folgende Zweierpotenzen dienen der Erzeugung des Bruches -->
  <xsl:param name="maxiter" select="38"/>
  <!-- Anzahl der Iterationen. 38 ist mehr als ausreichend, um wenigstens auf 10 Kommastellen genau zu arbeiten -->
  <xsl:variable name="x" select="$number * $number"/>
  <xsl:choose>
    <xsl:when test="$k &gt;= $maxiter">
      <!-- Runden auf 10 Kommastellen -->
      <xsl:value-of select="$n + round($frac * 10000000000) div 10000000000"/>
    </xsl:when>
    <xsl:when test="$x &lt; 10">
      <xsl:call-template name="ckbk:log10-util">
        <xsl:with-param name="number" select="$x"/>
        <xsl:with-param name="n" select="$n"/>
        <xsl:with-param name="k" select="$k + 1"/>
        <xsl:with-param name="divisor" select="$divisor * 2"/>
        <xsl:with-param name="frac" select="$frac"/>
        <xsl:with-param name="maxiter" select="$maxiter"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:log10-util">
        <xsl:with-param name="number" select="$x div 10"/>
        <xsl:with-param name="n" select="$n"/>
        <xsl:with-param name="k" select="$k + 1"/>
        <xsl:with-param name="divisor" select="$divisor * 2"/>
        <xsl:with-param name="frac" select="$frac + (1 div $divisor)"/>
        <xsl:with-param name="maxiter" select="$maxiter"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Die Hauptaufgabe von Ckbk:log10 besteht darin, das Problem der Berechnung von log10(x) auf das einfachere Problem der Berechnung von log10(x : 1 <= x <  10) zu reduzieren. Dies geschieht, indem man feststellt, dass log10(x : x > 10) = log10( x div 10) + 1 und log10(x : x < 1) = log10( x * 10) – 1. Da Logarithmen für null oder negative Zahlen nicht definiert sind, wird auch eine Fehlerprüfung durchgeführt.

Das Hilfs-Template ckbk:log10-util erledigt die schwere Arbeit. Es handelt sich hierbei um eine endrekursive Implementierung einer iterativen Technik, die bei Knuth gefunden wurde. (Anmerkung: Die zugrundeliegende Mathematik würde den Rahmen dieses Buches sprengen. Einzelheiten finden Sie in Knuth, D.E., The Art of Computer Programming, Vol. 1, S. 24, Addison-Wesley, 1973.) Um die Implementierung endrekursiv zu halten und sie außerdem stark zu vereinfachen, wurden verschiedene Buchführungsparameter definiert:

n

Der ganze Teil der Antwort, die durch ckbk:log10 geliefert wurde. Dieser Parameter ist nicht unbedingt erforderlich, da er hätte vorgehalten werden können, bis ckbk:log10-util seinen Teil der Arbeit erledigt hat. Allerdings entfällt hierdurch der Zwang, das Ergebnis von ckbk:log10-util in einer Variablen abzufangen.

frac

Der gebrochene Anteil der Antwort. Dies ist das eigentlich von uns Gesuchte.

k

Ein Iterationszähler, der bei jedem rekursiven Aufruf inkrementiert wird. Die Rekursion endet, wenn $k > $maxiter.

divisor

Eine Zahl, die bei jedem rekursiven Aufruf auf die nächsthöhere Zweierpotenz gesetzt wird (z. B. 2,4,8,16 ...). Der Wert 1 div $divisor wird zu frac addiert, während Sie den Logarithmus annähern.

maxiter

Die Anzahl der Iterationen, die verwendet werden, um frac zu berechnen. Je höher maxiter ist, umso größer fällt die Genauigkeit des Ergebnisses aus (bis zu den Grenzen der IEEE-Gleitkommawerte). Ein Parameter muss nicht verwendet werden, allerdings eröffnet er die Möglichkeit, log10 zu erweitern, so dass es dem Aufrufer erlaubt ist, die erforderliche Anzahl von Iterationen zu ermitteln und so an Geschwindigkeit und Genauigkeit herumzudrehen.

Potenz: ckbk:power(base,power)

Zurzeit sind bei EXSLT.org keine Implementierungen aufgelistet, die ckbk:power( ) unterstützen. Da es jedoch durch EXSLT definiert wurde, ist power( ) einfach in reinem XSLT zu implementieren. Jeni Tennison bietet folgende Implementierung:

<xsl:template name="ckbk:power">
  <xsl:param name="base"/>
  <xsl:param name="power"/>
  <xsl:choose>
    <xsl:when test="$power = 0">
      <xsl:value-of select="1"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="temp">
        <xsl:call-template name="ckbk:power">
          <xsl:with-param name="base" select="$base"/>
          <xsl:with-param name="power" select="$power - 1"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:value-of select="$base * $temp"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Für die meisten Anwendungen ist dieser Code ganz in Ordnung, allerdings ist er weder endrekursiv noch handelt es sich um die algorithmisch effizienteste Implementierung. Die folgende Implementierung ist endrekursiv und reduziert die Anzahl der Multiplikationen von O($power) auf O(log2($power)). Sie fügt außerdem eine Fehlerbehandlung hinzu, die eine unendliche Rekursion verhindert, falls $power eine NaN ist:

<xsl:template name="ckbk:power">
  <xsl:param name="base"/>
  <xsl:param name="power"/>
  <xsl:param name="result" select="1"/>
  <xsl:choose>
    <xsl:when test="number($base) != $base or number($power) != $power">
      <xsl:value-of select="'NaN'"/>
    </xsl:when>
    <xsl:when test="$power = 0">
      <xsl:value-of select="$result"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:power">
        <xsl:with-param name="base" select="$base * $base"/>
        <xsl:with-param name="power" select="floor($power div 2)"/>
        <xsl:with-param name="result" select="$result * $base * ($power mod 2) + $result * not($power mod 2)"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Dieser Abschnitt führt einen Buchführungsparameter namens $result ein, um die endgültige Antwort zu erzeugen. Er erlaubt es Ihnen, die Funktion endrekursiv zu machen. Bei jedem rekursiven Schritt wird die Basis quadriert und die Potenz halbiert. Da Sie floor( ) verwenden, erreicht $result 0 in ceiling(log2($power)) Rekursionen. Dies erklärt die bessere Leistung. Das Komplizierte ist die Berechnung von $result in jedem Schritt.

Wir wollen diesen Ausdruck einmal analysieren, indem wir uns die beiden Seiten der Addition anschauen. Der Ausdruck $result * $base * ($power mod 2) ist gleich $result * $base, wenn $power ungerade ist, ansonsten ist er 0. Umgekehrt ist $result * not($power mod 2) gleich 0, wenn $power ungerade ist, und ansonsten $result. In XPath 2.0 würden Sie diesen Ausdruck als if (power % 2) eq 1 then result * base else result schreiben. In XPath 1.0 emulieren Sie dies mit ein bisschen XSLT-Zauberei. Das Nettoergebnis sieht so aus, dass dieses Template schließlich b1 * base + b2 * base2 + b3 * base4 * b4 * base8 ... berechnet, wobei bi entweder 0 oder 1 ist. Es sollte ganz einfach zu sehen sein, dass diese Summe sich für eine beliebige Integer-Potenz bis basepower addiert, indem die bis auf entsprechende Werte gesetzt werden – was genau das ist, was der Ausdruck $power mod 2 ermittelt. Wenn dieses Konzept immer noch unklar ist, dann probieren Sie einige Fälle von Hand aus, um sich zu überzeugen, dass es funktioniert. (Anmerkung: Ungeachtet der Tatsache, dass formale Beweise der algorithmischen Korrektheit wichtig sind, finde ich sie nervend und langweilig, weshalb Sie in diesem Buch keine finden werden. Ich bevorzuge Intuition für das Testen meines Codes. Code-Generierung zeigt, wie die homoikonische Natur (in der die Programmform identisch ist mit der Datenform) von XSLT das Testen vereinfacht. Ein echter Informatiker würde alles durch Induktion beweisen und bräuchte keine Tests.)

Die zuvor gezeigte Potenzfunktion berechnet Potenzen nur für positive ganzzahlige Potenzen. Wie Ihnen jedoch jeder Abiturient sagen kann, ist xy eine reelle Zahl für alle reellen x und y, nicht nur für positive Integer-Werte. Es wäre doch schön, für den Fall, dass man eine braucht, eine verallgemeinerte Form von power( ) in der Hinterhand zu haben. Und tatsächlich, hier kommt eine. Um dieses Template nicht mit power( ) zu verwechseln, heißt es power-f( ), wobei das f für Floating Point (Gleitkomma) steht. Falls Sie es vorziehen, die allgemeinste Form mit power( ) zu bezeichnen, dann benennen Sie einfach Ihren eigenen Code um. Es ist dennoch sinnvoll, die eingeschränkte Form als separate Funktion zur Verfügung zu haben:

<xsl:template name="ckbk:power-f">
  <xsl:param name="base"/>
  <xsl:param name="power"/>    
  <xsl:choose>
    <xsl:when test="number($base) != $base or number($power) != $power">
      <xsl:value-of select="'NaN'"/>
    </xsl:when>
    <xsl:when test="$power &lt; 0">
      <xsl:variable name="result">
        <xsl:call-template name="ckbk:power-f">
          <xsl:with-param name="base" select="$base"/>
          <xsl:with-param name="power" select="-1 * $power"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:value-of select="1 div $result"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="powerN" select="floor($power)"/>
      <xsl:variable name="resultN">
        <xsl:call-template name="ckbk:power">
          <xsl:with-param name="base" select="$base"/>
          <xsl:with-param name="power" select="$powerN"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:choose>
        <xsl:when test="$power - $powerN">
          <xsl:variable name="resultF">
            <xsl:call-template name="ckbk:power-frac">
              <xsl:with-param name="base" select="$base"/>
              <xsl:with-param name="power" select="$power - $powerN"/>
            </xsl:call-template>
          </xsl:variable>
          <xsl:value-of select="$resultN * $resultF"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$resultN"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>  
<xsl:template name="ckbk:power-frac">
  <xsl:param name="base"/>
  <xsl:param name="power"/>   
  <xsl:param name="n" select="1"/>
  <xsl:param name="ln_base">
    <xsl:call-template name="ckbk:log">
      <xsl:with-param name="number" select="$base"/>
    </xsl:call-template>
  </xsl:param>
  <xsl:param name="ln_base_n" select="$ln_base"/>
  <xsl:param name="power_n" select="$power"/>
  <xsl:param name="n_fact" select="$n"/>
  <xsl:param name="result" select="1"/>
  <xsl:choose>
    <xsl:when test="20 &gt;= $n">
      <xsl:call-template name="ckbk:power-frac">
        <xsl:with-param name="base" select="$base"/>
        <xsl:with-param name="power" select="$power"/>
        <xsl:with-param name="n" select="$n + 1"/>
        <xsl:with-param name="ln_base" select="$ln_base "/>
        <xsl:with-param name="ln_base_n" select="$ln_base_n * $ln_base"/>
        <xsl:with-param name="power_n" select="$power_n * $power"/>
        <xsl:with-param name="n_fact" select="$n_fact * ($n+1)"/>
        <xsl:with-param name="result" select="$result + ($power_n * $ln_base_n) div $n_fact"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="round($result * 1000000000) div 1000000000"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Der Hauptanteil der Berechnungen wird nicht vom Template ckbk:power-f erledigt. Stattdessen führt es eine Fehlerüberprüfung durch und berechnet das Ergebnis dann über das existierende Template ckbk:power und ein neues Template ckbk:power-frac. Das Template ckbk:power-f nutzt die folgenden zwei mathematischen Wahrheiten über Potenzen:

  • Basis-y = 1 / Basisy, wodurch negative Potenzen einfach verarbeitet werden können.
  • Basis(potenz1+potenz2) = Basispotenz1 * Basispotenz2, wodurch Sie das präzise und effiziente ckbk:power( ) für den ganzen Teil von $power wiederverwenden und eine gute Annäherung für den gebrochenen Teil benutzen können.

Das Template ckbk:power-frac ist eine rekursive Implementierung der Maclaurin-Reihe für xy:

Maclaurin-Reihen für Potenzen

Abbildung: Maclaurin-Reihen für Potenzen.

Eine Möglichkeit, die trigonometrischen Funktionen zu implementieren, besteht darin, ähnliche rekursive Implementierungen ihrer Maclaurin-Repräsentation zu erzeugen:

Fakultät

Seltsamerweise hat EXSLT keine Fakultätsfunktion definiert. Die Fakultät kann natürlich einfach implementiert werden:

<xsl:template name="ckbk:fact">
  <xsl:param name="number" select="0"/>
  <xsl:param name="result" select="1"/>
  <xsl:choose>
    <xsl:when test="$number &lt; 0 or floor($number) != $number">
      <xsl:value-of select="'NaN'"/>
    </xsl:when>
    <xsl:when test="$number &lt; 2">
      <xsl:value-of select="$result"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:fact">
        <xsl:with-param name="number" select="$number - 1"/>
        <xsl:with-param name="result" select="$number * $result"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Eine sinnvolle Verallgemeinerung der Fakultät ist eine Funktion, die das Produkt aller Zahlen in einem Bereich berechnet:

<xsl:template name="ckbk:prod-range">
  <xsl:param name="start" select="1"/>
  <xsl:param name="end" select="1"/>
  <xsl:param name="result" select="1"/>
  <xsl:choose>
    <xsl:when test="$start &gt; $end">
      <xsl:value-of select="$result"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:prod-range">
        <xsl:with-param name="start" select="$start + 1"/>
        <xsl:with-param name="end" select="$end"/>
        <xsl:with-param name="result" select="$start * $result"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

XSLT 2.0

In 2.0 ist abs( ) integriert. Sie können die anderen für maximale Bequemlichkeit als Funktionen implementieren:

<!-- Potenz -->
<xsl:function name="ckbk:power" as="xs:double">
  <xsl:param name="base" as="xs:double"/>
  <xsl:param name="exp" as="xs:integer"/>
  <xsl:sequence select="if ($exp lt 0) then ckbk:power(1.0 div $base, -$exp) else  if ($exp eq 0) then 1e0 else $base * ckbk:power($base, $exp - 1)" />
</xsl:function>    
<!-- Quadratwurzel -->
<xsl:function name="ckbk:sqrt" as="xs:double">
  <xsl:param name="number" as="xs:double"/>
  <xsl:variable name="try" select="if ($number lt 100.0) then 1.0 else if ($number gt 100.0 and $number lt 1000.0) then 10.0 else if ($number gt 1000.0 and $number lt 10000.0) then 31.0 else 100.00" as="xs:decimal"/>
  <xsl:sequence select="if ($number ge 0) then ckbk:sqrt($number,$try,1,20) else 'NaN'"/>
</xsl:function>    
<xsl:function name="ckbk:sqrt" as="xs:double">
  <xsl:param name="number" as="xs:double"/>
  <xsl:param name="try" as="xs:double"/>
  <xsl:param name="iter" as="xs:integer"/>
  <xsl:param name="maxiter" as="xs:integer"/>
  <xsl:variable name="result" select="$try * $try" as="xs:double"/>
  <xsl:sequence select="if ($result eq $number or $iter gt $maxiter) then $try else ckbk:sqrt($number, ($try - (($result - $number) div (2 * $try))), $iter + 1, $maxiter)" />
</xsl:function>    
<!-- Fakultät -->
<xsl:function name="ckbk:factorial" as="xs:decimal">
  <xsl:param name="n" as="xs:integer"/>
  <xsl:sequence select="if ($n eq 0) then 1 else $n * ckbk:factorial($n - 1)"/>
</xsl:function>    
<!-- Produkt-aus-Bereich -->
<xsl:function name="ckbk:prod-range" as="xs:decimal">
  <xsl:param name="from" as="xs:integer"/>
  <xsl:param name="to" as="xs:integer"/>
  <xsl:sequence select="if ($from ge $to) then $from else $from * ckbk:prod-range($from + 1, $to)"/>
</xsl:function>    
<!-- Log10 -->
<xsl:function name="ckbk:log10" as="xs:double">
  <xsl:param name="number" as="xs:double"/>
  <xsl:sequence select="if ($number le 0) then 'NaN' else ckbk:log10($number,0)"/>
</xsl:function>
<xsl:function name="ckbk:log10" as="xs:double">
  <xsl:param name="number" as="xs:double"/>
  <xsl:param name="n" as="xs:double"/>
  <xsl:sequence select="if ($number le 1) then ckbk:log10($number * 10, $n - 1) else if($number gt 10) then ckbk:log10($number div 10, $n + 1) else if($number eq 10) then $n + 1 else $n + ckbk:log10-util($number,0,0,2,38)" />
</xsl:function>    
<xsl:function name="ckbk:log10-util" as="xs:double">
  <xsl:param name="number" as="xs:double"/>
  <xsl:param name="frac" as="xs:double"/>
  <xsl:param name="iter" as="xs:integer"/>
  <xsl:param name="divisor" as="xs:double"/>
  <xsl:param name="maxiter" as="xs:integer"/>
  <xsl:variable name="x" select="$number * $number"/>
  <xsl:sequence select="if ($iter ge $maxiter) then round-half-to-even($frac,10) else if ($x lt 10) then ckbk:log10-util($x,$frac,$iter + 1, $divisor * 2, $maxiter) else ckbk:log10-util($x div 10, $frac + (1 div $divisor), $iter + 1, $divisor * 2, $maxiter)" />
</xsl:function>

Diskussion

Ich schätze, dass wenigstens 80% Ihrer gewöhnlichen XSLT-Anwendungen niemals Berechnungen erfordern, die über die XPath-eigenen Fähigkeiten hinausgehen. Bei den verbleibenden 20 Prozent ist die Wahrscheinlichkeit recht hoch, dass eine oder mehrere der gezeigten Funktionen notwendig sind.

Der größte Nachteil einer reinen XSLT 1.0-Implementierung besteht darin, dass die Templates nie als erstrangige Funktionen vom XPath-Ausdruck aufgerufen werden können. Dadurch werden die Berechnungen schwierig und etwas ineffizienter, da Sie künstliche Variablen erzeugen müssen, um die Ergebnisse der Template-Aufrufe als Fragmente des Ergebnisbaums zu erfassen. Intern muss der XSLT-Prozessor diese Fragmente wieder in Zahlen umwandeln, wenn sie in nachfolgenden Berechnungen eingesetzt werden sollen.

Ein anderes Problem mit XSLT-Implementierungen besteht darin, dass die öffentliche Schnittstelle dieser Templates mit Buchführungsparametern »verunreinigt« ist, die das Wesen der Funktion verschleiern. Die Buchführungsparameter sind oft notwendig, um die Implementierung endrekursiv zu machen und unnötige Arbeit zu vermeiden. Beispielsweise muss die Implementierung von power-frac den Logarithmus der Basis berechnen und ihn ständig zur Verfügung halten. Wäre ln_base kein Parameter, dann würde log bei jedem rekursiven Aufruf ausgelöst werden, wodurch die Leistungsfähigkeit in inakzeptabler Weise herabgesetzt werden würde.

XSLT 2.0 löst dieses Dilemma, indem es Ihnen erstrangige Funktionen bietet. Die 2.0-Lösungen sind daher knapper und einfacher zu benutzen. Allerdings können Parameter in xsl:function keine Vorgabewerte haben. Sie müssen diese Fähigkeit daher emulieren, indem Sie Funktionen mit verschiedenen Stelligkeiten überladen, um Vorgabewerte zu emulieren. Leider bietet 2.0 keine Möglichkeit, die privaten Parameter zu kapseln, die in rekursiven Funktionen für Buchführungsaufgaben eingesetzt werden. Wenn Sie Funktionsbibliotheken entwerfen, könnten Sie etwa in Erwägung ziehen, die Implementierungsfunktionen (d. h. diejenigen mit den künstlichen Buchführungsparametern) in einen anderen Implementierungsnamensraum zu setzen, um anzudeuten, dass sie nicht für einen allgemeinen Einsatz gedacht sind.

Das zweite Problem würde gemildert werden, wenn eine künftige XSLT-Version private Parameter hätte, die nur in einem rekursiven Aufruf verwendet werden könnten.

  

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de