2009年12月13日日曜日

XSLT で sin を求める

XSLT で 三角関数を求めてみる。0度から90度までの sin が求まればいいので、定義域は[0, 90)とした。

やり方は、角度とsin値の対応表を作っておいて、その表と与えられた角度を照合し、表にない角度については単純に線形補間する。

線形補間は直線を折り曲げたカクカクしたグラフで大体の値を求めるやり方で、例えば、30度と45度が表にあるときの sin35度の値は以下のように計算する。
sin(35) ≒ sin(30) + ((35 - 30) / (45 - 30)) * (sin(45) - sin(30))
電卓で計算すると、以下のようになる。
左辺 ≒ 0.5736
右辺 ≒ 0.5690

XSLT 的なポイントとしては、RDB のマスタテーブル的な XMLファイルを用意しておいてXSLTから参照する事と、あとはテンプレートの再帰呼び出しのあたりか。

まず sin値の表を以下のように用意する。
<?xml version="1.0" encoding="UTF-8"?>
<sine-table>
<sine digree="0">0</sine>
<sine digree="15">0.259</sine>
<sine digree="30">0.5</sine>
<sine digree="45">0.707</sine>
<sine digree="60">0.866</sine>
<sine digree="75">0.966</sine>
<sine digree="90">1</sine>
</sine-table>

この表に基づいて sin値を計算する XSLT を、次のように定義する。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<result-list>
<xsl:apply-templates/>
</result-list>
</xsl:template>
<xsl:template match="digree">
<result>
<xsl:value-of select="."/>
<xsl:text> ⇒ </xsl:text>
<xsl:call-template name="sine0-90">
<xsl:with-param name="d" select="."/>
<xsl:with-param name="sines" select="document('sin-table.xml')/sine-table/sine"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="sine0-90">
<xsl:param name="d"/>
<xsl:param name="sines"/>
<xsl:choose>
<xsl:when test="$sines[2]">
<xsl:choose>
<xsl:when test="$d &lt; $sines[2]/@digree">
<xsl:variable name="a" select="$sines[1]/@digree"/>
<xsl:variable name="b" select="$sines[2]/@digree"/>
<xsl:variable name="fa" select="$sines[1]"/>
<xsl:variable name="fb" select="$sines[2]"/>
<xsl:value-of select="$fa + ($fb - $fa) * ($d - $a) div ($b - $a)"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sine0-90">
<xsl:with-param name="d" select="$d"/>
<xsl:with-param name="sines" select="$sines[position() > 1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>


この XSLT に下図左のXML を読ませると、下図右の出力XMLを得る。
<sin-test>
<digree>0</digree>
<digree>30</digree>
<digree>35</digree>
<digree>40</digree>
<digree>45</digree>
</sin-test>
<result-list>
<result>0 ⇒ 0</result>
<result>30 ⇒ 0.5</result>
<result>35 ⇒ 0.569</result>
<result>40 ⇒ 0.6379999999999999</result>
<result>45 ⇒ 0.707</result>
</result-list>

電卓の値と比較すると、だいたい合ってる。上の例は直角をたった6分割しただけの粗いものだけど、細かく分割すれば精度も上がる。

0 件のコメント:

コメントを投稿