前回までの記事
その1、その2で作ったプログラムを使うので、まずこちらから読んでください。
SwiftのDecimal型で数学の関数演算(その1)
SwiftのDecimal型で数学の関数演算(その2)
双曲線関数
双曲線関数は
\begin{align}
\sinh x &= \frac{e^x - e^{-x}}{2} \\
\cosh x &= \frac{e^x + e^{-x}}{2} \\
\tanh x &= \frac{\sinh x}{\cosh x} = \frac{e^x - e^{-x}}{e^x + e^{-x}}
\end{align}
のようにexpを用いて表すことができるので簡単に求まります。
双曲線関数もマクローリン展開で求めることができるのですが、三角関数と違って周期的でないため、値が大きい時の計算速度が気になったのでこの方法をとりました。
以下がプログラムになります。
extension Decimal {
func sinh() -> Decimal {
if isNaN { return .nan }
let texp = self.exp()
return (texp - 1 / texp) / 2
}
func cosh() -> Decimal {
if isNaN { return .nan }
let texp = self.exp()
return (texp + 1 / texp) / 2
}
func tanh() -> Decimal {
if isNaN { return .nan }
else if self > 45 { return 1 }
let texp = self.exp()
let _texp = 1 / texp
return (texp - _texp) / (texp + _texp)
}
}
特に難しいところもありません。
強いていうなら、tanhで引数が45よりも大きいとき1を返すようにしているのは、
$$\tanh 46 = 0.99999999999999999999999999999999999999977821219614$$
で、Decimal型の有効桁数38桁を考えるともうこれ1に丸められるので、計算の必要もないだろうと思って1を返すようにしました。
逆双曲線関数
これも公式があって
\begin{align}
\sinh^{-1} x &= \ln (x + \sqrt{x^2 + 1}) \\
\cosh^{-1} x &= \ln (x + \sqrt{x^2 - 1}) \\
\tanh^{-1} x &= \frac{1}{2} \ln (\frac{1+x}{1-x})
\end{align}
と表されるので、これをそのままプログラムで書くだけです。
extension {
func asinh() -> Decimal {
if self.isNaN { return .nan }
return (self + (self.mul(self) + 1).sqrt()).ln()
}
func acosh() -> Decimal {
if self.isNaN || self < 1 { return .nan }
return (self + (self.mul(self) - 1).sqrt()).ln()
}
func atanh() -> Decimal {
if self.isNaN || self.abs >= 1 { return .nan }
return ((1 + self) / (1 - self)).ln() / 2
}
}
階乗
for文を回すしかありません。
しかし、値がすぐに大きくなってオーバーフローしてしまうので、計算できる範囲だけを考えればループの回数は多くありません。
extension Decimal {
func factorial() -> Decimal {
if self.isNaN || self < 0 || self > 103 || self.floor != self { return .nan }
else if self <= 1 { return 1 }
let integer = (self as NSNumber).intValue
var result: Decimal = 1
for i in 2...integer {
result *= Decimal(i)
}
return result
}
}
まず、階乗は0以上の整数でなければ計算できません。また、階乗は値がすぐに大きくなり、実際に計算してみると$104!$でDecimal型の範囲を超えてしまったので、これ以上の数の階乗は計算できません。そのチェックが一番はじめのif文になります。
次のelse ifは$0! = 1! = 1$であり、次のfor文の範囲指定でエラーが出ないように入れました。
intに値を変換するところでは、値は必ず2以上103以下の整数であり、変換に失敗することがないのでエラーチェックは入れていません。
それができればあとはfor文で回すだけです。
あとがき
以上でこのシリーズは終わりとなります。
僕はこのプログラムを自分で作った電卓で使用していますが、電卓って世の中にありふれていて一見簡単そうに見えるけどプログラムを組んでみると実は難しいのです。
僕の作った電卓はRPN電卓であり、演算子の優先順位を考える必要がないので一般的な中置記法を用いる電卓よりもプログラムを組むのは簡単なのですが、それでもいろいろな関数に対応させるためにこの記事のようなプログラムは組まなきゃいけないし、ボタン押した時の動作を状態遷移図を用いて書き出して、それをswitch文で実装していくとかいう面倒なこともやってます。
なので、なんとなく電卓作ってみようと思ってこの記事を読んでる人は覚悟しておいた方がいいかもしれません。
(ちなみにRPN電卓は使いやすくていいですよ。慣れると普通の電卓が使えなくなるくらいに。コンパイラの仕組みとか知ってる人にはこの良さが分かるんではないでしょうか。)
お願い
RPN Anywhere
僕は上記の電卓アプリを公開しており、ここに載せたコードはこの電卓アプリのソースコードから引っ張ってきています。
もし、より正確に速く計算できる方法や、不具合などありましたら教えてください。