DEF FN函数活用ハンドブック
半人前プログラマーをDEF FN函数のエキスパートにする法
図表 DEF_FNFG.pdf
関連 DEF_FN+.pdf
執筆:水島哲生、The BASIC誌 1991/02〜/05号(#93〜#96)連載、技術評論社
16年前の'90年夏に書かれた古い記事で対象のN88BASICには既に動作環境がないが、表計算ソフトでの☆ユーザー定義函数作成技術に直結する内容なので、全文を当HPにupしておく.(Web.up.執筆者承認済み.表計算ソフト用のユーザー定義函数解説を今後まとめようと思っている.
See→Excel/123でのVBA函数定義)
(ブラウザ
Fire Foxのリンク機能が一部動作しない。その場合はブラウザIEを使うか、文字列検索 ctrl-Fで代用されたし。Fire Foxではラベルに小数点「
.」を含むリンクが動作しない。
('10/08/19))
DEF FN函数活用ハンドブック
半人前プログラマーをDEF FN函数のエキスパートにする法
- 【0】 序
- 【T】 実態とずれている定義:「函数とは何か!」
教科書・参考書にみる「函数(関数)とは」
- 定義1.y=f(x)型定義
- 定義2.集合・写像型定義
- 定義3. [[函数とは]]
- 定義4. 対応関係が函数
- 【U】 ステップ・バイ・ステップで「函数概念」の拡張を
- [1] 「DEF FN+陽函数」で定義完了!
- [2] 境界値・特異値・定義域処理にカギ
- [3] 独立変数が複数の函数:演算型処理は総て函数である
- [4] 独立変数を持たない函数!:集合型や文字型も当然「函数」
- -(2) 画面座標も無独立変数型函数
- -(3) 座標変換には適する DEF FN
- -(4) 「機能番号」よりも「名称」が楽
- [5] 小数部桁落ちには充分注意
- -(2) 倍精度VAL
- -(3) 倍精度CDBL
- 【V】 複数行定義を可能にする DEF FN の多重多段引用
- [2] 小数部函数:FRC(X)
- [3] 区間・特異点スイッチは論理式で
- -(2) 論理式の値
- -(3) 特異点対策にも応用
- -(4) 大選択、小選択
- [4] キー入力補助としてDEF FN
- [5] 定係数も巧く使おう
- (2) とことん使う有効桁数
- [6] 誘導函数の定義
- [7] 角度の値域変換
- [8] 角度の加算・減算
- [9] 全方位アークタンジェント
- X,Y2値入力型ATN函数
- ユーザー定義関数(DEF FN)化
- [10] 右廻り座標/左廻り座標
- -(2) 左右勝手違いの実計測
- -(3) CRT座標は右(時計)廻り
- [11] 座標変換はDEF FNの独壇場
- [12] 度分秒変換、度変換
- 【W】 演算は「系」全体として考えよう
- -(2) スタックメモリーを増やして使う
- -(3) 多重引用を予定する函数は文字列型をなるべく避ける
[[練習問題T]]
- 【X】 DEF FN函数応用の具体例
- [1] 正弦法則の活用と「負の角度」、「負の辺長」概念への拡張
- -(1) 二辺と一方の頂点角から他の頂点角
- -(2) 「負の角度」、「負の辺長」概念への拡張
- [2] 第2余弦法則の活用と「負の角度」、「負の辺長」概念への拡張
- -(1) 二辺と挟角から他の一辺
- -(2) 「負の角度」、「負の辺長」概念への拡張
- -(3) 三辺長から頂点角を求む:第2余弦定理
- -(3.b) 三角形の三辺長から面積を求む(補足節)
- 1:第二余弦定理、3平方の定理より
- 2:ヘロンの公式
- -(4) 二辺長と挟角から頂点角を求む
- [3] トライアンドエラー法による逆函数近似:
- -1). ローン(年金原価)函数
何が主たる問題かの分析が大切
- -2). 試行錯誤法は電算機に依存
- -a). ニュートンの求根法での函数利用
g(x)=0 の選定
- -b). 意外に簡単!利息率逆算へのニュートン求根法の適用
- [4] LC共振周波数函数:こんな形でもOK!
- [5] 使える DEF FN函数
- -a). 数値文字列の符号を取り払う
- -b). 数値の余白桁を指定文字列で埋める
- -c). 余白桁を'0'で埋める
- -d). 符号付で'0'で埋める
- -e). 16進数の余白部を'0'で埋める
- -f). 小数以下有効桁指定
- -g). 数値の書式指定汎用函数
- 【Y】 「汎用サブルーチン」 VS 若干の優位:「DEF FN函数」
- -2). DEF FNで劇的な効果??
- -3). 手計算そのままのオンメモリー化も妥当
- -4). 結局はアルゴリズムに帰着
- -5). たたき大工方式では………
- 【Z】 わかり易いDEF FN定義
- -2). DEF FNエキスパート≠一人前プログラマー
- -3). 各分野の専門家の方が
- -4). DEF FN演習問題 U
【図表一覧】
- 表T 単精度小数と倍精度小数
- 表U TAN(X)のエラーとバグ
- 表V 動径の角度と座標
- 表W 角象限毎のアークタンジェントのオフセット角
- 図T 一意対応
- 図U 単精度実数型変数のデータ表現
- 図V アークタンジェントと動径座標(X,Y)
- 図W 右(時計)廻り座標/左(反時計)廻り座標
- 図X-a. 逆方向の統一法:正弦法則
図X-b. 逆方向の統一法:第2余弦法則
- 図Y 余弦法則
- 図Z ニュートンの求根法
- [リストT] DEF FN函数定義 サンプル
- [リストU] ニュートンの求根法 サンプル
- [リストV] DEF FN函数使用法 サンプル
序
DEF FN 函数は BASICと数学・論理演算の知識をほぼそのまま活かせるので、本来なら応用範囲が広くて大変有用なはずである。ことにメモリー容量の少ないBASICマシンでは劇的な効果を発揮することがある。
しかしBASIC言語自体の地位が実状よりも著しく過小評価されている現状も影響してか具体的解説も理論的解明もあまりなされずに放置され、プログラマー個々の自然成長に任せた職人芸に留まっている。かの林晴比古氏によれば『DEF FN文がうまく使いこなせるようになれば,プログラマも一人前であろう』(「BASICによるプログラミング・スタイルブック」、S60年12月、ソフトバンク出版部)というのである。学ぶ者への激励なのだろうが、真実とは少しズレがあると思う。そこで私は「アマノ邪鬼ザベ」にふさわしく『半人前プログラマをDEF FNエキスパートにする法』として「職人芸の一般化」を目指して整理して書き連ねることとする。
DEF FN 命令の特長は、文字どおり「函数」であるから、
- 定義した後は、sin,cos など元々の組込み函数と全く同じ扱いとなる。
- データを与える変数名は何でもよく、定義行とは別のものを使ってよい。
- 定義行で、別の DEF FN函数を複数個引用することができる。
引用する DEF FN函数内で更に他のDEF FN函数を引用できる。
- 独立変数の数に制限はない。(BASIC自体の制限である1行255バイト制限と、文字列処理バッファー容量、スタック・メモリー容量の制限は受ける)。
という性質があり、プログラムを作成する場合、
- 処理を抽象化して処理の見通しをよくすることができる。
- 演算型のサブルーチンのほとんどがこのDEF FNで表現することができ
- ローカル変数を利用できないBASICでも変数の衝突を気にせずライブラリーとして使える
- 演算のメンテナンスが定義部1ヶ所に集中して管理しやすくなる。
- テキストメモリー消費を節約できることが多い。
といった利点がある。
その使い方は、値を求めたい未知数を「陽函数」の形に整理して、冒頭に「DEF FN」を付けると定義が完了し、あとは使うだけ。単純明快である。ところが、その高機能の DEF FN が充分有効に活用されてはいない。使われない原因の解明を含めてDEF FN函数の詳細な使用法をまとめようとする試みが本稿である。
BASICとFORTRANはゴキブリ並に必ず生き残る。個人の作業を支えるツールとしては気軽に使えて一貫して頼りがいのある現役の言語である。その利用が廃れたことは一度もない。日常の設計計算の手順を「C」でプログラムする技術者はまずいないだろう。
ヴァケーションライターに堕した私の昨年夏休みのバーコードに続いて今年90年の夏休みはDEF FN函数で読者の皆さんと再会となる。若干長めであるが決して少数派ではないソフト自作派には必ず役に立つので最後までおつきあい願いたい。
【1】実態とずれている定義:
「函数とは何か!」
函数という文字は英語のfunctionを中国語にするときに同音の'函'を当てたことを直輸入したものと聴いているが、最近の高校以下の教科書では総て'関数'と書かれている。これは概念の表記法の問題だから使用上はどちらでも良い。オジさん世代は'函数'がやや多かったのだが、若造りがお好みの方は後者の'関数'を用いればよい。
DEF FNとは利用者が考案した函数のメモリーコピー手続きである。利用者の頭脳にない概念が突然RAM中に発生することはない。だがDEF FNの包含する函数概念と学校教育で各人に形成された y=f(x) という函数概念とを比べるとかなりのズレがあるようだ。これが DEF FN の理解と有効利用を妨げているのではないだろうか。先日、町田駅ビルの本屋で高校生に混じって数学Tの参考書を物色したところ、あるある!何と60種類を越える参考書が陳列されている。他教科はせいぜい10〜20種であるから「数学T」は高校生諸君にとってはかなりの鬼門なのだろう。参考書の函数定義についての記述を捜すと、全く記載のないまま、いきなり一次関数、二次関数の説明にはいるものがその2割余、y=f(x) 型定義が7割、集合・一次変換と写像型が残り1割といったところで、しかも、函数について例外のないそのものズバリの定義は一つもなかったのである。中学高校生諸君がかなり抽象化された概念である「関数概念」の理解に戸惑うのも無理はないと思った。
教科書・参考書にみる「函数(関数)とは」
まず、昔なつかしい函数定義はどうだったか。
定義1. y=f(x) 型 定義
[[函数とは]]
二つの変数x,yがあって,xのとる値がきまれば,それに応じてyのとる値がきまるとき,yはxの函数であるという。
xの函数を表わすには,ふつう記号 f(x) を用いる。
yがxの函数であることは y=f(x)で表わされる。
[逆函数] y=f(x) の文字x,yを交換した関係式 x=f(y) を考え,
これをyについて解いて得られるxの函数 y=g(x) を,
もとの函数 y=f(x) の逆函数という.
資料出所:「応用数学」:文部省検定済高校教科書。矢野健太郎他、昭和37年2月10日績文堂発行
この本は工業高校で採用されていた教科書である。数学T参考書のほぼ7割がこの流れをくむ定義だった。現在ではこの定義を1対1対応に改めたものが中学2年生の学習参考書に採用されている。
しかしながら
- @独立変数が複数の函数
- A非数値型函数
についてはこの定義には収まらないし、多価函数を考慮した記述ではない。
多くのプログラマーの認識も(実務ではこの限界を越えて作業している人でも)定義は?とせまられると、その多くがこの y=f(x)型の解答をする。
定義2. 集合・写像型定義
函数概念の定義が最初に出てくるのは、昔は高校1年だったように記憶しているが、今は中学2年生の数学である。昔の中2では三角比のことを三角函数と呼び慣わしていた向きもあるだろうが、抽象化した函数概念としては学んでいなかった筈である。それが今は中学3年生で以下のような「恐怖の定義」を行なっている。
[[函数とは]]
集合Xから集合Yへの一意対応によってXの要素xにYの要素yが対応するとき,yはxの関数であるという。
[対応]
2つの集合X,Yが与えられているとき,何らかの規則(関係)で,集合Xの要素に集合Yの要素を結びつけることを「集合Xから集合Yへの対応」という。
[一意対応]
集合Xのおのおのの要素に対応する集合Yの要素が,それぞれに1つだけある対応を「集合Xから集合Yへの一意対応」という。
[1対1対応]
次の@,Aの性質が成り立っている対応を1対1対応という。
@ Xのおのおのの要素にYの要素が1つだけ対応している。
A Yのどの要素にも,それを対応の相手とするXの要素が1つだけある。
資料出所:中学数学の基本事項:3年:1989改訂版、植竹恒男他、駿台文庫
同趣旨の定義が高校の数学Tでは以下の様に述べられている。
集合Aのどの要素xをとっても,集合Bの要素yがただ一つ決まるという対応のこと
資料出所:解法へのパスポート:数学T、矢野鍵太郎、1986年改訂版、科学振興社
うちのアホ娘とドラ息子は中3になったからといって、果してこんなものを正確に理解できるのだろうか?という慄然たる思いはさておいて、
y=f(x)型 よりはスマートに「認識の歴史的発展」を遂げて、DEF FNに関していえば、その機能を包含するものとなっている。しかしながら、多価函数はこの定義から外れる。
この本の解説でも一意対応でないものは「函数ではない」と説明されている。直交座標系でグラフで表わすと楕円になる函数の場合、x,yどちら側から見ても1対2対応となる。だからこの定義では円や楕円を描くx,yの関係は函数ではなくなってしまう。多価函数、陰函数、などの概念を必要とする分野とは違う方面(集合論)からのアプローチだからズレを生じているのだろう。そうした事情は理解できるのだが、定義で明確に「違う!」とされたものが、後ほど突然何の説明もなく「多価函数」などといって出現するのでは全国で「数学嫌い」を大量生産することにならないだろうか。
定義4. 対応関係が函数?
以上の全てを包含できる函数定義は意外なところに記述されていた。
定義4.
[[函数とは]]
"yがxの函数である"というのは,xの値にyの値が対応していることである。
資料出所:「数Vの研究」:S36年改訂版、森繁雄・清宮俊雄、旺文社.(導函数の章、「極値」、「定義域」の解説中で)
一意対応でなくても排除していないのである。筆者の舌足らずによる「限定落ち」なのだろうか、それとも、これでよいのだろうか。
なお大学教養課程での「函数論」というのは、複素積分、線積分……と数冊分あって「ごくフツーの学生」であった私にとって遥か忘却の彼方の項目がならんでいるのだけれど「函数とは」という最も基本的な定義は全く記載がなかったのである。また、私の周辺は多くが理数系の人たちであるが、学校を卒業したばかりの若い人も含めて函数の認識は y=f(x) 型であった。
この初歩的な定義が一般常識として受け取られている状態で DEF FN の能力を充分に使いこなせというのはなかなか大変である。日常経験による自然成長に任されるから『プログラマーとして一人前になるだけの経験を積んだとき DEF FN文がうまく使いこなせるようになっている』のだろうと思う。それなら、DEF FNの例題を示しながら、函数概念の拡張とDEF FNの有効な使い方を示せば、読者各人の専門・固有技術に基づく函数式が自然にDEF FNとしてメモリーに定着されるはずではないか。裏返していえば、RAMに定着させるべき函数を持たない人にはDEF FNは使えない、あるいは、持てる数学力の範囲でDEF FNを使うことが出来るというべきである。だからDEF FNが難しいのではない。本気で取り組んで難しいと感じるのであれば、それ以前の処理函数自体の理解や整理に障害があるのではないだろうか。
【2】ステップ・バイ・ステップで
「函数概念」の拡張を
函数概念自体が抽象化する概念なのだが、具体例で認識を広げる方が実践的なので順を追って例題を積み上げることにする。例題に一定の納得がいったら再度「函数とは」の項を読み直すときっと理解が深まるだろう。
[1] 「DEF FN+陽函数」で定義完了!
未知数を陽函数の形に整理して、冒頭に「DEF FN」を付けるとDEF FNの定義は完了する。それ以上でも以下でもない。プログラムとしては単純きわまりない記述である。
(陽函数というのは y=f(x) の形に整理されていて、yの値を直接計算できるものを指している。
陰函数というのは f(x,y)=C という形で表わされて値を直接計算できないものをいう)。
重ねて指摘すれば、函数というのはsin,cos,tan,atn,sinh,log,expなどの一般的な数学函数だけを指すのではない点に特に留意してもらいたい。ピタゴラスの定理や、正弦法則、余弦法則などをいずれかの未知数を求める陽函数の形に変換すれば、それはもう冒頭に DEF FN を冠してプログラムラインとし、一度実行するだけでBASICの組込函数になるのである。
DEF FN利用の基本問題は「アルゴリズムをどの様な内容の陽函数に整理すれば最も合理的か」という(プログラム技術ではない)それぞれの応用分野の固有技術に帰着するのである。
[2] 境界値・特異値・定義域処理にカギ
プログラム特有の問題としては、極値・特異値・エラーの処理であるが、これは原理的にはサブルーチン方式でも同じである。但しエラーを起こした場合の行番号が函数を引用した行ではなくDEF FNの定義行になるうえ、変数名も自由に使えるので再現が複雑でデバッグが難しくなるのである。だからDEF FNを使う場合には定義域と極値・特異値を厳密に処理して綿密な事前デバッグを済ませてから組み込むべきである。テストでいえば並の答案ではなく、常により厳密な模範解答が求められているから、演算のアルゴリズムについてより厳密な理解を要求されるのである。もっとも専門家ではない一般の人が利用するソフトの場合には、オペレータにトラブル対処能力を期待すべきではないからサブルーチン方式であっても模範解答的解析が要求される。この点で考えればDEF FNだからといって解析に特段の注意を要求される訳ではない。実質は手抜き解析のヤッツケ仕事になってしまった場合に後で起こる事態の解決が困難さを増す程度の違いである。このことは、あくまで、各応用分野の固有技術上の問題でありDEF FN自体が難しいのではないことは納得して頂けるだろう。
[3] 独立変数が複数の函数 演算型処理は総て函数である
y=Ax+B ……(A≠0)
というとき、A,Bが定数であれば函数としては無論xの一次式であり函数形としては
y=f(x)=Ax+B …(A≠0)
と表記してきた。
ところでこれを一般形でプログラムする場合はどうかというと、XだけでなくA,Bも変数として扱って値を入力するのではないだろうか。すなわち
y=f(x,A,B)=Ax+B ……(A≠0)
というのが厳密に考えた場合の函数形である。これは3つの独立変数をもつ函数である。
DEF FNを使って定義すれば
DEF FNF(X,A,B)=A*X+B ……(A<>0)
である。
2次函数についても同様で実質は
y=f(x,A,B,C)
=Ax2+Bx+C …(A≠0)
という4独立変数の函数である。
DEF FNF2(X,A,B,C)=A*X*X+B*X+C
で函数定義される。
通常の演算処理は、多数の独立変数を参照して、最終結果を導く作業であるから、途中経過を同一演算処理内に組込んでしまえば結局「多変数の函数」に帰着する。実務上の問題として、アルゴリズムをどう分割して一般化し、プログラムラインを分かりやすくして、デバックとメンテナンスのやり易いソフトにするかということである。
サブルーチン内の式を一つ一つ DEF FN化しても、処理の抽象化:明確化の他にはあまり効果が上がらない。逆に必然性がないのに、全処理行程を1本のDEF FNにまとめることは見通しや保守性を悪くしてナンセンスである。一定単位のまとまりのある行程を、一般形としてDEF FN化して使って利用実績のできた枯れたラインを「ライブラリー」として持つのが効率的であろう。
[4-(1)] 独立変数を持たない函数! 集合型や文字型も当然「函数」
y=f(x)型よりも拡張された前出の函数の定義からいえば、集合型や文字型も函数である。とはいえ「独立変数を持たない函数」というのは、一般的な数値型の函数概念からは理解にかなり抵抗のあるところである。そこで実例としてADコンバータからの読み取りを考えてみよう。
ADコンバータとは測量値(計測値)の集合の各要素をデジタル値の集合に変換する装置である。データや制御コマンドのやりとりのためにポートアドレスが割り当てられているが、これは計測デジタル値の集合の要素ではない。変換函数は「デジタル値の集合の要素」を「表示・記録値の集合の要素」に変換するのだから、函数内に独立変数は要らないのである。
DEF FNADI=CONST*(INP(ADRS)+256*INP(ADRS+1))-CONST2
(ADRS:A/Dに割当てたI/Oポートアドレス、 CONST,CONST2は変換定数)という訳だ。
これも、ADRS,CONST,CONST2を独立変数と考えることも可能である。
DEF FNADI(ADRS,CONST,CONST2)=(INP(ADRS)+256*INP(ADRS+1))*CONST-CONST2
この場合、3パラメターが決まっても測量値である函数の値は当然決まらない。温度だの電圧電流だのの被測量値のA/D変換出力(計測デジタル値)が必要である。
[4-(2)].画面座標も無独立変数型函数
CRTスクリーン上のカーソルやスポットの座標も独立変数を持たない函数である。確かにY座標を得る「CSRLIN」は独立変数を持たないし、X座標を得る「POS()」の括弧内の数値はダミーで何でも良いから実質は独立変数がない。
N88BASICのグラフィック座標については
POINT(機能番号)
という函数があるが、これも厳密にいえば独立変数がない函数に分類されるべきである
。集合の要素は各スクリーン座標であり機能番号ではないからである。
しかし実使用上はそれほど厳密に考えずに機能番号を独立変数として扱って全く支障はなく、無変数の場合を除くと理論上の相違だけである。
[4-(3)].座標変換には適する DEF FN
CSRLINやPOS(0)を単にDEF FNに置き換えたところで、函数名称変更による処理の明確化の意味しかないが、グラフィック画面と、テキスト画面の座標変換用として定義すると、座標系を統一できるので画面設計が分かりやすくなる。だが、単純な比例関係ならば変換定数を定義してこれを乗ずる方が函数定義よりスマートである。
1110 SX%=640/80 : SY%=400/25
と宣言してから
LINE(18*SX%,Y*SY%)-STEP(6*SX%,SY%)
などの形で使用する。
[4-(4)].「機能番号」よりも「名称」が楽
POINT(機能番号)函数の場合には、処理内容が直感的には判らないから名称変更に意義が出てくる。
直近に処理したスクリーン座標(GX,GY)を求めるのは以下の通りである。
1160 DEF FNSX=POINT(2)
1170 DEF FNSY=POINT(3)
ワールド座標(TX,TY)での値もN88-BASICには準備されている。
1130 DEF FNGX=POINT(0)
1140 DEF FNGY=POINT(1)
総て独立変数を持たない函数である。どちらの函数が分かりやすく引用部での間違いが少ないかは一目瞭然である。
[5] 小数部桁落ちには充分注意を!
【表T】 単精度小数と倍精度小数 <TBL1>
X → CDBL(X) | | X → CDBL(X)
|
---|
| | | |
|
1.00!= | 1.000000000000000# | | 1.50!= | 1.500000000000000#
|
1.01!= | 1.009999990463257# | | 1.51!= | 1.509999990463257#
|
1.02!= | 1.019999980926514# | | 1.52!= | 1.519999980926514#
|
1.03!= | 1.029999971389770# | | 1.53!= | 1.529999971389771#
|
1.04!= | 1.039999961853027# | | 1.54!= | 1.539999961853027#
|
1.05!= | 1.049999952316284# | | 1.55!= | 1.549999952316284#
|
1.06!= | 1.059999942779541# | | 1.56!= | 1.559999942779541#
|
1.07!= | 1.070000052452087# | | 1.57!= | 1.570000052452087#
|
1.08!= | 1.080000042915344# | | 1.58!= | 1.580000042915344#
|
1.09!= | 1.090000033378601# | | 1.59!= | 1.590000033378601#
|
1.10!= | 1.100000023841858# | | 1.60!= | 1.600000023841858#
|
1.11!= | 1.110000014305115# | | 1.61!= | 1.610000014305115#
|
1.12!= | 1.120000004768372# | | 1.62!= | 1.620000004768372#
|
1.13!= | 1.129999995231628# | | 1.63!= | 1.629999995231628#
|
1.14!= | 1.139999985694885# | | 1.64!= | 1.639999985694885#
|
1.15!= | 1.149999976158142# | | 1.65!= | 1.649999976158142#
|
| | |
|
1.20!= | 1.200000047683716# | | 1.70!= | 1.700000047683716#
|
| | |
|
1.30!= | 1.299999952316284# | | 1.80!= | 1.799999952316284#
|
| | |
|
1.40!= | 1.399999976158142# | | 1.90!= | 1.899999976158142#
|
| | |
|
1.48!= | 1.480000019073486# | | 1.98!= | 1.980000019073486#
|
| | | 2.00!= | 2.000000000000000#
|
BASICを起動して試しに
PRINT CDBL(1.23) Enter
と打鍵して頂きたい。画面には
1.230000019073486
と表示される。
単精度小数と倍精度小数の対応表
[表T]をみると一見心配になるくらい数値が違う。その原因はといえば、マイクロソフト系のBASICで小数を扱う場合、内部処理が浮動小数点の純2進数なので10進数の小数に対しては必ず誤差を生じているが、表示ルーチンで末尾の4捨5入操作を行なってその誤差を見えなくしているためである。単精度の値を倍精度に変換する場合、本来なら単精度数値の末桁未満の桁落ち誤差の補正をするべきだが、これが行なわれていないから桁落ち誤差が現れる。これは VAL("文字列")函数でも指定する有効桁数が少なければ同じである。
VAL(STR$(X)) の値を調べていて気付いたのだが、これを USING "#.##…(小数部15桁)…#"に適用すると正しく倍精度に変換されるが X#=VAL(STR$(X)) では CDBL(X) の場合と変わらない。動作の基準がはっきりしないのも困ったものだ。
純科学計算で基本単位ばかりを使う場合や、事務処理で整数ばかりを使う場合にはこの精度が変えられない問題は障害にならないのだけれど、度分秒変換などが入ると誤差の影響によって純論理としては正しいのに小数部に大きな誤差が生じることがある。この誤差を充分配慮して函数を構成する必要がある。これは DEF FN に限ったことではないが、エラーの発見が難しいので特に慎重に扱った方がよいということである。大抵の処理では1/50台もの誤差が現われては問題外である。
[5-(2)] 倍精度VAL
まず、倍精度VAL函数は、VAL函数が8桁以上の有効桁数を指定すると倍精度変換をする性質を使って、小数部に文字列のゼロを付け加えている。但し、原数値文字列に小数点がない場合にはゼロの数だけ桁が狂うのでゼロの前に小数点を付け加えている。(1210行)
[5-(3)] 倍精度CDBL
倍精度CDBLは、倍精度VAL函数をそのまま引用して、数値を文字列に変換してから引き渡せば良い(1310行)。単に文字列にして数値に戻すだけでは倍精度は保障されない。
1200 ' 倍精度VAL ↓ 小数点付加 ↓ 0 付加
1210 DEF FNVALU#(X$)=VAL(X$+STRING$(-(INSTR(X$,".")=0),".")+"00000000")
(更に指数表示モードでの誤動作対策を行なうと以下のラインとなる)
1210 DEF FNVALU#(D$)=VAL(D$+FNVALDP$(D$)+STRING$(8,"0"))
1220 DEF FNVALDP$(D$)
=STRING$(-(INSTR(D$,".")=0 OR INSTR(D$,"E-")>0),".")
1300 ' 倍精度CDBL
1310 DEF FNCVDBL#(X)=FNVALU#(STR$(X))
【3】複数行定義を可能にする
DEF FN の多重多段引用
これまでの函数定義で重要なことをまとめて置こう。
- @ DEF FN の中に別の DEF FNを引用することができ
- A どちらを先に宣言しても支障なく、
- B 同名の函数は後から宣言した方が採用される
- C DEF FNの函数名に付ける変数型宣言は有効だ
ということである。
副函数は利用する順序にかかわりなく実際の函数利用までの間に宣言すればよいし、実行途中で函数の定義内容を変更して使用することもできるから動作条件により函数定義を変えながら処理することもできる。
函数の型宣言で函数名に$を付けると出力が文字列である函数となり、#を付けると演算精度が上がる。
また多段引用を許す性質は実用上大変な価値がある。函数内容を分割して定義する副函数を区間毎に定義して、これを合成する形で目的の函数を定義することによってDEF FNの持つ1行以内という制限を突破して、かなり自由に函数定義することが出来るからである。どの函数も副函数として他から引用できるので、中間段階の副函数を定義して多段階に函数引用を重ねればスタック・メモリー・エリアの許す限り更に大規模の函数を定義して利用することができる。
[2] 小数部函数:FRC(X)
小数部を求める函数FRC(X)はHPやテクトロなどの科学技術用コンピュータには内蔵函数として準備されている。これをDEF FNで構成しようと言うのであるが、内部演算に浮動小数点2進法を用いる機種では前出の小数部2進表記誤差による桁落ちで思わぬ落し穴にはまって苦労することになる。マシンがBCD(2進化10進法)演算であれば全く支障ないのだが、マイクロソフト系の様に純2進演算では小数部函数を2重に引用する函数定義は回避するか、または函数の精度をあらかじめ限定して、最大桁落ち誤差以上の補正値を常に加算して桁落ちを無くさなければならない。
原型のFRC(X)は
1410 DEF FNFRC(X)=X-FIX(X)
と函数定義される。これが多重引用によってどの様な誤差を起こすか実験して見よう。
X=1.30:Y=FNFRC(X)*100:
Z=FNFRC(FNFRC(X)*100):?X,Y,Z
と打鍵すると画面には以下の様に表示される。
1.3 30 .999996
末尾の FNFRC(X) を二重に引用した'Z'の値:.999996 がまるでおかしい。本来なら0の筈である。これはYの値: 30 が実際は 29.999996 であるのをシステムの表示ルーチンが末尾を丸めて表示したからに他ならない。
[表1]を眺めると半数は桁落ちしそうである。これでは怖くて使えない。
PC-98のユーザーズマニュアルに変数構造の記載
[図U]があって、これによると仮数部の小数部が23ビットあって24ビット目が桁落ちすることが明らかなので、桁落ちの修正は演算途中で倍精度変換して扱う場合には24ビット目に加算することが必要である。
1/2
23=1/ 8,388,6081.1921E-07
1/2
24=1/16,777,2165.9605E-08
この補正を行なって「小数部函数」を定義すると以下の通りである。
1410 DEF FNFRC(X)=BRW#*X-FIX(BRW#*X)
1420 BRW#=1.00000006#
上記の桁落ち修正を施したFNFRC(X)函数で同じ操作をすると今度は
1.3 30 5.6147E-06
と表示される。これは10-8台だから誤差としては単精度の範囲であるが、度分秒を用いる場合、整数部が3桁になると0.5秒前後の誤差を生ずる精度である。
[3] 区間・特異点函数切り替えは「論理式スイッチ」で
区間により記述函数が変わる場合、区間毎の各記述函数に論理式を用いた区間スイッチを設けて、区間外の値を与えるとゼロを示すようにしておく。これらの区間記述式を総て加算すると全定義域についての函数を構成できる。
定義域の区間nの函数をgn(x)とするとき、定義域全体の函数としては
g(x)=g1(x)+g2(x)+g3(x)+g4(x)+……
gn(x)=-fn(x)*(Xn<=x AND x
……… と定義する訳である。
[3-(2)] 論理式の値
「論理式の値」というのは、論理式の内容が「偽」(成立していない)ときにゼロ、「真」(成立している)ときには「−1」、(あるいは機種によって希に「+1」)となる。だから区間判定の論理式を各項に乗ずることによってスイッチとなる。
「真」の値が「+1」なのか「−1」なのかを調べるには
?NOT 0 Enter だとか
?5>4 Enter
?"X">"A"
などと打鍵して値を調べればよい。
IF文でIFとTHENの間に記述される条件式の判断が「論理式の値」として定義されているのである。BASICでは逆符号の「−1」が主流となっている。しかし、中には論理式が「真」で「+1」なのに「NOT 0」が「−1」という Sharp PC-1350 ポケコンの様なユーザー泣かせの機種もあるので、綿密な事前デバッグをするに越したことはない。
論理演算のブール代数では「1」が「真」だったが、「真」が「−1」の場合、乗ずる条件項の数が奇数項あると積の符号が反転するので項全体の極性には充分注意が必要である。実際上ここが論理式による演算のバグの源と言ってよい。私自身それがわかっていながら繰り返し間違えてバグチェックに引っかかるやっかいな部分である。この点に関してはBASICリファレンス・マニュアルの「概要」-「論理演算」項で「論理演算子は演算の前に扱う数値を-32768から+32767までの2の補数表示の整数に変換します。………指定された論理演算では,この整数に対してビットごとに演算を行ないます.」と述べていて16ビット全部が'1''真'の場合、補数表示により−1となることが解る。設計思想の問題であり善し悪しではないことは触れて置くが実使用でエラーの素になっている。
[3-(3)] 特異点対策にも応用
この方式は、分母をゼロにする特異点にも応用できる。分母をゼロにする入力があった場合、エラーを起こさないダミー値を与えて演算して、この項を区間スイッチによりゼロにして影響をなくすればよい。真値は同時加算をとている別項の記述式から与えられる。
[3-(4)] 大選択、小選択
大小比較選択は良く利用する。論理に忠実にコーディングすれば、大きい数を選択する函数は
1520 DEF FNMAX(X,Y)=-X*(X>=Y)-Y*(X<Y)
式を変形して定義すると
1510 DEF FNMAX2(X,Y)=X+(X<Y)*(X-Y)
と若干スマートになるが、小数部での桁落ち誤差が反映される危険を考えると私は前者の定義式をつかう。
同様に小さい数を選択する函数も
1530 DEF FNMIN(X,Y)=-X*(X<=Y)-Y*(X>Y)
が直接的で分かりやすい。
[4] キー入力補助としてDEF FN
BASICのINPUT文は、カーソルキーをいじると変なデータを拾ったり、CLRキーで画面全部が消えてしまうなど、一般使用者向けソフトのデータ入力を任せるにはかなり頼りない存在である。帳票作成型ソフトを請け負うシステムハウスの最大の「売り」がカラーで美しい画面と、画面に入力可能な1文字入力ルーチンであることは少なくない。
というのも実処理が基本的には表集計と4則演算とSORTであり、結果をファイルに記録しプリントアウトするだけの仕様なら、高度の技術演算が入ることはないし、技術的内容をいくら説明しても商売にならないからである。
この1文字入力時のチェックにDEF FN函数は有効に働く。演習を兼ねて例を挙げよう。
キー入力1キー毎に入力をチェックして案内や警告が出来ると入力の使い勝手は大幅に向上する。チェック内容としては@数値、小数点、Aアルファベット、B16進数、C入力範囲外、D数値+アルファベット、Eシフトキーのセンスと言ったところだろうか。
1600 ' X$が数字であることの判定
1610 DEF FNNUMS(X$) ="0"<=X$ AND X$<CHR$(ASC("9")+1)
1630 ' X$が数字であり、小数点の場合は付加されるE$内に小数点不存在の判定
1640 DEF FNNUM(X$,E$)=FNNUMS(X$) OR X$="." AND INSTR(E$,".")=0
1700 ' X$がアルファベットであることの判定
1710 DEF FNALP(X$) =("A"<=X$ AND X$<="Z") OR ("a"<=X$ AND X$<="z")
1800 ' X$が数値かアルファベットである判定
1810 DEF FNALNUM(X$)=FNNUMS(X$) OR FNALP(X$)
1900 ' X$が16進数であることの判定
1910 DEF FNHEXA(X$)="A"<=X$ AND X$<="F" OR "a"<=X$ AND X$<="f") OR FNNUMS(X$)
2000 ' 数値が値域外であることの判定←(処理の明確化)
2010 DEF FNOUTX(X,H,L)=X>H OR L>X
2100 ' PC-9801でシフトキーが押されていることの判定
2110 DEF FNSHIFT=(INP(&HE8) AND &H40)=0
[5] 定係数も巧く使おう
[度]の方が分かりやすいというのは人間側でありマシンの側ではないから、マン-マシン-インターフェイス部で常用単位系と基本単位系の相互変換を行なえばオペレータ側の要求も満足する。
この変換/逆変換は単純な比例関係であれば変換定数を設定する方が DEF FN より保守性が良くなる。
2410 PAI#=3.141592653589793#+2.385D-16
2420 RAD#=PAI#/180#
2430 DEG#=180/PAI#
と定義しておけば
PRINT SIN(30*RAD#) Enter
PRINT ATN(1)*DEG# Enter で良いから、更に DEF FN を定義して利用する必然性に乏しくなる。
[5-(2)] とことん使う有効桁数
2410行のπ(PAI#)の定義で末尾に指数表示の項が加算されている。これは変数メモリーのうち表示には使われない末尾ビットまで数値を定義して演算誤差を少なくしようとするものである。N/N88-BASICでは3ビット余:約1桁が表示されず末桁処理用に使われている。科学技術用のワークステーションでは表示桁数+2桁〜3桁を実際の内部演算桁として桁落ち誤差が目立たない様にしている。この部分にも布数するとその分演算誤差を少なくできることが多い。
PRINT ATN(1#)*DEG# Enter
によって実験すると、陰桁補正なしでは '45.00000000000001' であるが、陰桁補正によって正しく '45' を表示する。これは'精度'の問題というよりもむしろ'気分'の問題であり'商品価値'の問題であろう。なおプログラムラインに全桁を打鍵してもリストを取ると16桁表示になるので「リストが同じなのに動作結果が微妙に違う」気味の悪い現象を起こすので(2410行の様な)注意が必要だ。
敢えてこの変換を函数定義すれば
2510 DEF FNSINDEG(X)=SIN(X*RAD#)
2520 DEF FNCOSDEG(X)=COS(X*RAD#)
でよい訳だが、主処理内での基本単位採用の原則に照らせば演算処理の錯綜する規模の大きいシステムほど採用を避けたいし、また現状での DEF FN に対する心理的負担を考えるとこれは変換定数方式の方がメンテナンス担当者から嫌われないで済みそうだ。必然性の薄い論理数演算が含まれたりすると最低の評判になりかねないのだから。DEF FN敬遠派が主流である現在、「無用のDEF FN化は避けよう!」というのも DEF FN解説の触れるべき内容なのだろう。
[6] 誘導函数の定義
函数電卓では当然備えている逆函数機能がBASICでは自分で算出しなければならない。これを「級数収束法」などを用いて力づくで算出する場合で演算速度が問題にされ、あるいは標準ルーチンとして多用する場合には DEF FNよりもむしろマシンコードを用いてユーザー函数を定義した方がよい。
コードのセットは
CLEAR -INT(-VOL/16) 'VOLはコード量
DEF SEG=………マシンコードのセグメントアドレス
DEF USRn(X)= 〃 オフセットアドレス
BLOAD "コードファイル名" ですむ。
しかし、アセンブラを使って浮動小数点の数値演算を行なうのは正直な話かなりおっくうであり、他の函数の変形で実現できるのなら一般的にはDEF FNの方が利用価値が高い。
[6-(2)] sin-1θ、cos-1θ 函数の作成
三角函数の逆函数でN88BASICに準備されているのはアークタンジェント:ATN(X)函数のみであるが、三角函数の相互関係式を利用すればアークサインとアークコサイン函数を構成できる。(式変形の詳細は略)
sin2(X)+cos2(X)=1
sin(X)
tan(X)=―――――
cos(X)
∴TH=sin-1(X)
=ATN(X/SQR(1-X2))
X=±1で、TH=±π/2
TH=cos-1(X)
=ATN(SQR(1-X2)/X)
X=0でTH=±π/2
従って
2610 DEF FNACSIN(X)=ATN(X/SQR(1-X^2-(X^2=1)))*(X^2<>1)-SGN(X)*(X^2=1)*PAI#/2
2620 DEF FNACCOS(X)=ATN(SQR(1-X^2)/(X+(X=0)))*(X^2<>0)-(X=0)*PAI#/2
分母をゼロにするなどの特異点に注意して函数定義すれば、特別の困難はない。sinh,cosh,なども簡単に定義可能である。
[7] 角度の値域変換
角度は1周360度を過ぎると元に戻るのだから、使用場面に依って値域が異なる。一般的には±180度(±π)であろうが航空機や船舶の航法や測量では北を0として時計回転に360度を「方位角」として採用している。実使用ではこうした「±型」と「正値型」の相互変換が必要である。
方位角(0〜2π:360度)を±π(180度)に変換する函数は
2810 DEF FNPNTH(X)=X+2*PAI#*(X>PAI#)
逆に、±πを方位角に変換する函数は
2830 DEF FNDRC(X)=X-2*PAI#*(X<0) である。
[8] 角度の加算・減算
函数の値域が限定されると角度の加算、減算に依って生ずる域外値を補正する加算・減算函数も必要である。
角A,Bの値域を方位角(0〜2π:360度)とする加算と減算は、
2910 DEF FNADDTH(A,B)=A+B+2*PAI#*(A+B>2*PAI#)
2920 DEF FNSUBTH(A,B)=A-B-2*PAI#*(A<B)
と定義できる。
値域の変換は前出の値域変換函数を引用することもできるが、どちらの値域を主として扱うかにより、±πの値域で直接算出する角度加減算函数が望ましい場合もある。その場合の函数は
XXXX DEF FNADDTH(A,B)=A+B+2*PAI#*(A+B>PAI#)
XXXX DEF FNSUBTH(A,B)=A-B-2*PAI#*(B-A>PAI#)
となる。
[9] 全方位アークタンジェント
「全方位アークタンジェント」というのはどうだろう。タンジェント函数は±90[度]に不連続があって、180[度]周期の函数だから、航法システムなどでその逆函数:ATNを利用して1周360[度]の方位角を算出しようとすれば「IF THEN ELSEの山」になりかねない。これをDEF FN一発で求めようという仲々スリリングな例題である。
(注:この函数は現在では表計算ソフト123-2001、Excel-2003やJAVAにATAN2(x,y)函数などとして採り入れられている.この記事内容は1991/04発行(1990/08著述)でその先駆けとなっている)
BASICのTAN()函数に単精度の数値を代入して
TAN(+-1.7014118E +38)
を求めると、何とエラー無しの「0」になってしまうのでバグ対策として必ずエラーの現れる倍精度で扱うこととする(表U参照)。
【表U】 TAN(X)のエラーとバグ
| X | ATN(X)= |
|
---|
| | |
|
倍精度 | +-1.7014118346046922D +38 | 1.570796326794897 | [RAD]
|
単精度 | +-1.7014117E +38 | 1.57080 | [RAD]
|
+-1.7014118E +38 | 0 | (単精度ATNのバグ)
+-1.7014119E +38 | 0Verr | OVエラー で範囲外
| |
DEF FNでエラーが発生しては回復処理ができないから、独立変数の値を事前に定義域内に収める工夫や、異常値トラップが必要になる。tanθの絶対値が1.7014118E+38よりも小さければエラーを起こさないのだから、使用範囲を検証してみる。たとえば
最大総延長が 10[q]=10+7[o]
測寸分解能が0.1[o]=10-1[o]
とすれば、Y/Xの最大値は10+8であり、使用範囲の数値ではエラーにはなり得ないことが判る。
X,Y−2値入力型ATN函数
ATN函数の場合、原函数がπ(180度)周期の函数なので1個の独立変数からπ(180度)以下か以上かを判断出来ない。そこで、XY座標上を回転する動径のX成分,Y成分を与えることによって1周の角度を得ることにする。
tanθ=Y/XであるからY軸上(X=0)が特異値となる。この特異値の部分を切り放して定義し最後に合成すればよい。
【表W】 各象限のオフセット角
象限 | 条 件 | 補正値 | 演算適用条件
|
---|
| | |
|
T | (X>0)*(Y>0) | 0 | --------
|
U | (X<0) | π | (X<0)
|
V
|
W | (X>0)*(Y<0) | 2π | (X>0)*(Y<0)
|
- 1. X=0 の場合
- Y=0 では角度θは算出不能であるが、処理を中断させないためのダミー数値としてERRORにならない数:ゼロを選ぶ。
- Y>0 の場合、θ= π/2
- Y<0 の場合、θ=3π/2
- 2. X>0 の場合
- Y>0:第T象限:θ= ATN(Y/X)
- Y<0:第W象限:θ= ATN(Y/X)+2π
- 3. X<0 の場合:第U〜V象限
【表V】動径の角度と座標
象限 | 方位 | 方位角 |
| 条 件 | | 角度加算 補正値
|
---|
X | Y | Y/X
|
---|
| | | | | |
|
第T象限 | 北−東 | 0〜 90 | X>0 | Y>0 | + | +0度
|
(+Y軸) | 東 | 90 | X=0 | ∞ | ------
|
第U象限 | 東−南 | 90〜180 | X<0 | − | +180度
|
(−X軸) | 南 | 180 | Y=0 | 0
|
第V象限 | 南−西 | 180〜270 | Y<0 | +
|
(−Y軸) | 西 | 270 | X=0 | ∞ | ------
|
第W象限 | 西−北 | 270〜360(0) | X>0 | − | +360度
|
(+X軸) | 北 | 0 | Y=0 | 0 | ------
|
ユーザー定義関数(DEF FN)化
3010 DEF FNACTN(X,Y)= FNR0(X,Y)+FNR1(X,Y)
3020 DEF FNR0(X,Y)= (0.5-(Y<0))*PAI#*(X=0)*(Y<>0) 'X=0:±90゚
3030 DEF FNR1(X,Y)=-ATN(Y/(X+(X=0)))*(X<>0)+(-(X<0)+(X>0)*(Y<0)*2)*PAI#
PAI#=3.141592653589793#+2.385D-16' (20桁)
以上のラインを通過した後であれば
PRINT FNACTN(A,B),FNACTN(X#,Y#)
:R2=FNACTN(Q1#,Q2#)
などと使用することができる。値域を±πとするには前出の値域変換函数を作用させて
PRINT FNPNTH(FNACTN(X,Y))
などとして使えばよい。
[10] 右廻り座標/左廻り座標
「方位角」の出てきたところで、右廻り座標と左廻り座標について触れておいた方がよいだろう。
数学で学ぶ標準的な直交座標は+X軸を右手方向,+Y軸を上方向として+X軸を角度原点:0度とする左廻り座標である。各軸を境にT象限からW象限までに区分されている。
方位角の座標は北を+X軸,東を+Y軸とし,+X軸を角度原点:0度とする右(時計)廻り座標であり、時計廻り方向にT〜W象限と角度が定義されている。慣れないと扱いにかなり戸惑うが、理論上の扱いは右廻り左廻り相互に方程式Y=Xを対象軸にする鏡対照の関係にあるから、角度の回転方向が正反対であることを除いては全く同様に扱ってよい。
Y=Xの直線上に置いた鏡を介して観測すれば逆廻りの座標上の現象に見えるのだから、代数的に方向とX,Yを取り違えない様注意すれば理論解析や演算処理は同一でよい。
[10-(2)] 左右勝手違いの実計測
ところが、実計測となると逆廻り座標では困難を生ずる。計測装置が総てその分野で標準的な回転方向で作成されているので、裸の読み取りだけの装置でも換算が必要となるし、演算処理を行なうインテリジェントのものは全くお手上げになってしまうことがある。
測量関係ではトランシット/セオドライトを用いるがこの方位角目盛りは総て右廻りである。羅針盤(などと今時呼ばない!コンパス)の目盛りも同じく北を0度とする右廻り目盛りであり航行にあたって相互に共通の尺度だから絶対に左廻りはない。
[10-(3)] CRT座標は右(時計)廻り,一部右回り!
コンピュータ・ディスプレーの画面もテキスト画面、グラフィック画面ともに直交座標としては右(時計)廻り座標である。+X軸が右手方向であることは数学座標と同じだが+Y方向は下向きであり、横軸を角度原点とする右廻り座標である。数学座標との対比では直線Y=0を鏡対照の軸とする座標系であり、先の方位角座標を右に90度回転させ実方位と切り放したものである。
ところが、N88-BASICはグラフィック画面での角度の指定が左廻りとなっており、座標系との統一が保たれていない。角度の左廻りを正方向にするのなら、グラフィック座標も上方向を+Y方向にすればイレギュラーな変換をせずに作図できる。テキスト画面の方向に準じたというのであれば、CIRCLE文の回転方向は右廻りに統一されるべきだったと思う。実用面でこれを見ても円グラフは時計廻りに描かれていくのが普通ではないか。同一画面なのに場合によって変換方法が異なるというのはいただけない。
極座標表示や方位角を問題にするソフトウェアを手掛ける場合はシステムの基本設計として、どの部分に右廻り座標、左廻り座標を採用するのかを厳密に吟味することが必要である。
[11] 座標変換はDEF FNの独壇場
「座標変換」も「数学教育の鬼門」の様であるが、日常生活では何の抵抗もなく「座標変換」を行なっている。座標変換というのは、原点と基準の方向と基準寸法を定めて、その基準により表記していた位置(座標)を、別の原点と基準の方向、尺度で表わし直すことである。土地の地理に明るければ「都の西北1里」を座標変換して「高田馬場駅東方3q」とか「上野駅より西方4q南方に2q」とか言われても京都市育ちの人以外は大抵は困らない。平面の極座標表示と直交座標表示が渾然一体となってわれわれの日常会話を構成している訳である。京都市街は例の完璧な直交座標系であり、「烏丸五条上ル」などと南北・東西の直交する大通りの名前で位置指定することに慣らされているから、東京の様な無秩序の街にくると迷子になりやすいのである。どうしようもない方向音痴というのはそうは居ない。この座標変換が「数学教育の難問」になってしまうのは生徒のデキよりもむしろ数学のカリキュラムや教えかたの方が悪いと考えるべきだろう。
平面座標の変換で最も混乱する「直交座標の回転変換」の函数化を試みよう。変換の考え方としては、実は「極座標表示」への変換を介して、回転角分を減算してから再び別の直交座標に戻してくる訳だが、演算を整理すると、回転した座標系からみた新座標は元のX,Yの値と座標の回転角θの三角函数だけで表示できる。
X=x cosθ−y sinθ
Y=x sinθ+y cosθ
最近の教科書は「一次変換と写像」の章でこの変換式を採り上げて行列式を使って次のように書き表している。
直交座標回転変換行列
| X |
| = |
| cosθ, | -sinθ
| | ×
| | x
|
| ………(行列、作用素表示)
|
|
| | |
|
Y |
| sinθ, | cosθ | | y
|
函数定義はX,Yそれぞれ1行ずつで以下の通りである。
3210 DEF FNROTX(X,Y,TH)=X*COS(TH)-Y*SIN(TH)
3220 DEF FNROTY(X,Y,TH)=X*SIN(TH)+Y*COS(TH)
この函数は、移動計測や誤差補正で傾き補正のために多用される。計測装置を積んだ車両・船舶や航空機などの架体が傾けばその角度分を補正して対地座標へ変換するためである。この回転変換を架体内座標での水平軸合わせと、対地絶対座標への変換の2段階に使って絶対位置を算出していく訳であるが、プログラムラインとしては1段階が1行で済んでしまう。
ところが、昔の高校の教科書にはこれが掲載されて居なかった!(今の教科書にはまず載っている!現在の高校生諸君にとってこれは、よろこべ!なのか?御愁傷様!なのか?)。この回転変換を、幾何学的作図によって解析し各象限毎にIF THEN で分解してその場その場で新座標の値を求めていたらプログラムラインの量は膨大なものになる。このDEF FNが単純なのはアルゴリズムが単純なためである。DEF FNがシンプルにした訳ではない。
[12] 度分秒変換、度変換
60進の度分秒を度を境の小数点で区切って DDD.MMSS で表わすと入出力の使い勝手がよいので、函数電卓にはこの方式が採用されているが、純2進法の演算でこの方式を採用すると、前出の桁落ち対策のために計算精度を0.5分より良くすることはできない。しかし計測器の出力自体が度分秒というのも多いので換算函数を準備した方がよい。
? FNDMS#(21.5000),FNDEG#(21.2960)ヒEnter
などとして使用する。精度は0.5分余、10進法では 0.00014 程度のまるめによる誤差が含まれている。
' DDD.MMSS → DDD.DDDD 変換
3510 DEF FNDEG#(X#)=FNDMI(X#)+FNDF1#(X#)+FNDF2#(X#) 'DMS$-->DEG
3520 DEF FNDMI(X) =FIX(X*BRW#):BRW#=1.00000006# '整数部
3530 DEF FNDF1#(X#)=FIX(FNFRC#(X#)*100)/60#
3540 DEF FNDF2#(X#)
=FIX((FNFRC#(X#)*100-FNDF1#(X#)*60)*100+SGN(X#)/2)/3600#
' DDD.DDDDDD → DDD.MMSS 変換
3610 DEF FNDMS#(X#)=FIX(X#)+FNMM#(X#)/100#+FNSS#(X#)/10000#
3620 DEF FNMM#(X#)=FIX(FNFRC#(X#)*60)
3630 DEF FNSS#(X#)=FIX((FNFRC#(X#)*60-FNMM#(X#))*60+SGN(X#)/2)
【4】演算は「系」全体として考えよう
各種計測システムの様に算出値が幾つもあって、それが互いに関連しているような場合、演算処理は基本単位と基本場によって行なう方がシンプルでバグも発生しにくくなる。利用する算出式に変換定数がついたり、各値を演算に引用する毎に変換定数を乗ずるのは決してスマートではないばかりかバグ発生の温床になりかねない。こうした個々の変換に DEF FNを用いることはできるが卓効は期待できない。複雑な演算処理は「MKSA有理単位系」などの基本単位系を用いて、角度の単位もBASICでの基本単位でもある[rad]に統一して用いた方がシンプルになる。
「有理単位」というのは現象を空間の立体角:4π[ステラジアン]で基準化する単位系を指している。各種定義式にやたらと4πが出現する。
では、どんな場合も基本単位優先かというとそうではない。システムとして実用単位系で総て(三角函数もほとんど使わずに)完結していて、主要部で変換の必要が全くない場合に敢えて基本単位に変換し逆変換する必要はない。
DEF FNは演算の主部に適用した方が有効なことが多い。DEF FN化する場合にはアルゴリズムをシビアに詰める必要から、より合理的なアルゴリズムを発見する場合もあり、更にスマートになる。
[4-(2)] スタックメモリーを増やして使う
FOR NEXT,WHILE,DEF FN やサブルーチンを使うとユーザー・スタック・メモリーが使われる。複雑なDEF FN ではスタック・エリアを使い尽くして Out of memory error がでることがある。スタック領域の大きさは CLEAR文で指定することができる。MS−DOS版N88−BASICでは CLEAR文の第2パラメター、NEC機添付のN88−BASIC(86)では第3パラメターがスタックメモリー容量を指定する。確保される容量は与えたパラメターの16倍である。無指定の場合のデフォルト値は&H20で、その容量は&H200=512バイトである。
但し、スタック領域を増やすとその分テキストエリアが減少するから、テキストエリアの空容量:FRE(1)の値に注意を払い両方の兼ね合いを考えて拡張する必要がある。
[4-(3)] 多重引用を予定する函数は文字列型をなるべく避ける
文字列に関するDEF FNを多重に使うとかなり頻繁に
string fomuiar is too complex
という errorが発生する。この場合、函数の構成を全く変えないと使えないことが多い。だから、多重引用が予定される場合は文字列処理型が多重に引用されるのをなるべく避ける必要がある。単独でデバッグする時には正確に働くのに多重引用では全く動かなくなるのだから始末がわるい。
[[ DEF FN 練習問題を-1]]
ここで、DEF FN定義の練習問題としよう。
【問題】 極座標−直交座標変換のDEF FN函数を定義せよ。
- @ 平面座標系
- A 地球儀型極座標系
(赤道が緯度〇度、西経が正)
- B 数学型極座標系
(天頂:北極が緯度〇度、南極が180度、東経が正)
以上の例題は「数学の例題?」という印象の半分は正しい。しかし、「例題」と称して既に整理された陽函数を示して「冒頭に文字列の
'DEF FN函数名(パラメターリスト)='
を付けろ」というのでは全く練習にならないではないか。定義するに当たっての数学的解析処理こそがDEF FN定義の実体なのである。だからこそBASICのマニュアルだけをいくら読んでもDEF FNは使えるようにならないのだ。
この問題は更に、幾何学的な作図を介しての処理をなかなか寄せ付けず、代数的に一次変換などからのアプローチの方が変換式が単純になり使用に適している。両方の解き方で定義して比較してみると良いだろう。全象限を作図で解いて一本に合成できる方はなかなかのベテランなのだろう。1950年代以降生まれの方は数学に写像と一次変換、集合などがドサッと加えられているから、昔のテキストをひっくり返すとほぼそのままの解答が見つかることが多い。それ以前生まれのオジさんは「実力で難関突破」も頼もしいが、硬くなった頭を改めて認識して自信喪失するのは有害無益だから早めに子供たちの参考書を拝借する方がよい。OR(オペレーションズ・リサーチ)だの、LP(リニアー・プランニング:線形計画法)だの、新記号法だのと面白そうな項目が並んでいて興味をそそるが、これらを一律に生徒たちに詰め込んだら「数学好き」が増えるのだろうか「数学嫌い」が増えるのだろうか、そこが問題だ。「試験はできたけど後は見るのもイヤ!」と「ヘタの横好き」とを較べてどちらが「数学的思考力」を身につけたと言えるのだろうか。エッ!子供の作り方を習わなかった!?それをいま教えても16年待ってるわけに行かないから、赤提灯のビール1本セットの値段で参考書を手に入れればよろしい。教科書とは違って読者の共感のないものは廃れ、面白いのが再版を重ねて残っている。
【5】DEF FN 函数応用の具体例
さてこれまでで、DEF FN 函数作成についての基本的解説を終えた。DEF FN が必要になれば、この内容を敷衍して函数をつくって定義すればよいのであるが、どこでも使いそうな小道具的函数や、特別な用途への適用例、もうひとヒネリした函数などを具体的に示しておいた方がいろいろ応用・発展しやすいし、各人の標準ライブラリーを構成しやすくなるので更に切口の若干異なる応用例を示しておく。
[1] 正弦法則の活用と「負の角度」、「負の辺長」概念への拡張
三角形の二辺の長さと一方の頂点の角度から、二辺の挟角を求める場合、正弦法則から求めることが便利である。[図X]に示す三角形ABCに於て各頂点の対辺の長さをそれぞれa,b,c,三角形に外接する円の半径をRとするとき,各辺と角度の間に以下の等式が成り立つ(証明略)。
[ 正弦法則 ]:(円周角一定の法則の系)
a ―――― | b =――――
| c =―――― | =2R
|
---|
sin A | sin B | sin C |
|
---|
この関係を「正弦法則」と呼ぶ訳であるが、この内の一つを未知数に選んで算出する陽函数形に変形すればDEF FN函数として使える。
a
sin A=――― sin B だから
b
A=sin-1(a・sin B/b)
C=2∠R−(A+B)=2∠R−{sin-1(a・sin B/b)+B}
である。これは先ほどのアークサイン函数がそのまま引用できるから
4010 DEF FNSINANG(A,B,TH)=FNACSIN(A*sin(TH)/B)
4015 DEF FNSINANGC(A,B,TH)=PAI#-FNACSIN(A*sin(TH)/B)-B
によって±π/2(±90度)を値域とする函数が定義できた。
幾何学的な作図による解法は人間が作業する上では分かりやすくて論理エラーの発生を抑えることができる。その際、求める角の方向が正負両側にある場合には、IF THEN によって方向別に分けて3角形を解いて、後で方向を示す符号を付けるのがオーソドックスな計算法である。ところがコンピュータ処理はこうした幾何学的扱いよりも、最初から方向:極性をもたせて代数的に扱う方が適している。その点で「なまじ函数を利用しながら IF THEN 分割再合成法を採用するのは幾何学的処理との折衷であまりスマートでない」のである。
[図X-b]で代数的統一の方策を検討してみよう。巧くいけば、負の辺長と負の角度の導入により正負の函数を統一できて便利である。辺aに負方向の長さを許容して、角度を計測する方向を左廻りが正、右廻りが負の左廻り座標系としよう。そうすると辺aが負方向の場合今までは三角形の頂点の角度であったBは、新基準の角度計測では「補角」扱いになり、正側と同じ側の角度が採用される。補角のsinは同値だから算出値は見事極正が負になっている。即ち、辺長aと角度Bを代数的に方向を決めて扱えば、算出値である角度Aも代数的に方向(符号)付けした値が得られて、正負を全く同一処理にすることができるのである。
図Yの三角形で各辺について
c2=a2+b2−2ab cos C
の関係がありこれを「第2余弦法則」と呼ぶが、C=0 と C=π では2乗法則:(A±B)2=A2+B2±2AB:と同結果であり、C=π/2ではピタゴラスの定理そのものであるから厳密な証明は参考書に譲るとしても、正しそうな気がしてくるだろう。
ここでは三角形の二辺の長さと、その挟角が判っている場合の未知の1辺の長さを求める函数を定義する。それは両辺の平方根をとれば良いから
4110 DEF FNCOSLEN(A,B,TH)
=SQR(A*A+B*B-2*A*B*COS(TH))
正弦法則と同様、負方向についての論議をすると、結果的にやはり同一式で処理できることがわかる。辺bの負の辺長を考慮しても、cosの角度が補角となって COS(π-TH)=−COS(TH) として極正が逆転するため算出式としては代数的に全く同一となるのである。この検討をしたことによって、演算を正側と負側に分割する必要がなくなった。
以上のような大量の解析情報が1行に詰まっている場合、ラインを一見して内包する情報の全てを理解することは不可能に近いが、「一見して解らないラインは使わない」という方針は明らかに行き過ぎである。標準ライブラリーとして全く同形で準備しておくのであれば、函数名を内容の分かりやすいものに統一する工夫は必要であるが解析とインターフェイスを記したドキュメントを準備しておけば充分便利に使えるではないか。演算内容の分かりやすさはサブルーチン方式と変わらない。
4100 ' 二辺とその挟角から一辺長を求む:余弦法則
4110 DEF FNCOSLEN(A,B,TH)=SQR(A*A+B*B-2*A*B*COS(TH))
4140 ' 三辺から一角を求む
4150 DEF FNCOSANG(A,B,C)=FNACSIN(SQR(1-((A*A+B*B-C*C)/A/B/2)^2))
4180 ' 二辺とその挟角から他の一角をを求む:(余弦法則+正弦法則)
4190 DEF FNTRITH(A,B,TH)=FNSINANG(A,FNCOSLEN(A,B,TH),TH)
[5-(2)-3] 三辺長から頂点角を求む:(第2余弦定理)
なお、三辺の長さから各角度を求める函数も定義しておこう。
a2+b2−c2
cos C=―――――――――
2ab
cos C=sin(C+π/2) だから
前出のアークコサイン函数やアークサイン函数を引用すればよい。アークコサイン函数は±π/2を区別できないから、ここでは敢えてアークサイン函数でアークコサインを求めてみよう。
sin2C=1−cos2C
C=sin-1(SQR(1-cos2C)) であり、先に cos C の値を求めているから DEF FN 函数は以下のように定義される。これは COS の逆函数だから、
cos X=cos(−X) であり正負の方向を区別できないからこの式を解いても「負方向の角度」を定義することはできない。
[2-(3.b)] 三角形の三辺から面積を求む(補足節)
[三角形面積1:第2余弦定理+3平方の定理]:
原記事の補足:三角形の三辺から面積を求めるのは土地測量の基本.三角形の絡む実用例には欠かせない.
面積算出方針としては、第二余弦定理から1角の余弦cosを求め、それをsinに換算、角を挟む二辺a,bと角Cの正弦から、三角形面積=a・b・sin(θ)/2 として計算する.
すなわち
c2=a2+b2−2a・b・cosC ………(第二余弦定理)より
cosC=(c2−a2−b2)/2a・b
|
辺長をそれぞれa,b,c、
その対角をそれぞれA,B,Cとする.
|
一方、
sin2C+cos2C=1 ………(三平方の定理の系)
sinC=sqrt{1−cos2C} となるから、
sinC=sqrt[1−{(c2−a2−b2)/2a・b}2]
∴面積S=a・b・sinC/2 となる。
これをユーザー定義函数化すると
DEF FNSINC(a,b,c)
=sqr(1-((c^2-a^2-b^2)/a/b/2)^2)
DEF FNSQUARE(a,b,c)=a*b*FNSINC(a,b,c)/2
と定義され、使用時は
PRINT FNSQUARE(a,b,c)
等と非常に簡単になる。
[三角形の面積2:ヘロンの公式]
(原記事の補足:)
面積=sqrt{s・(s−a)(s−b)(s−c)}
s=(a+b+c)/2 …………………(ヘロンの公式)をそのまま函数化
DEF FNS(a,b,c)=(a+b+c)/2
DEF FNSQUARE(a,b,c)=sqr(FNS(a,b,c)*(FNS(a,b,c)-a)*(FNS(a,b,c)-b)*(FNS(a,b,c)-c))
で定義する。使い方は前節と同じ。
有名公式を採るか、分かり易い算出順序を採るかの選択はクライアントの指定がなければプログラマーの好みだが、どちらの算式で計算しても、当然、結果は同じである。
[2-(4)] 二辺長と挟角から他の頂角を
三角形の二辺の長さとその挟角から他の頂角を求めるのは、余弦法則と正弦法則を縦続して用いて、先ず未知の1辺長を求めて、これにより他の頂角を求めればよい。定義式は 4190行である。
記述できる逆函数が存在しない場合、方程式f(x)=0の根を手計算で求めることは困難であるが、プログラマブルの計算機(電卓も含む)を使って力づくで指定精度内の近似値を算出する方法がいくつか考えられている。その一つであり、単純なアルゴリズムでありながら高精度・高速度演算の期待できる「ニュートンの求根法」の例について触れておきたい。
[3-(1)]ローン(年金原価)函数
利息がrの資金をX万円借りてN年の分割払いで返済する場合の、毎年返済額Aと総返済額Tを求めるというのは明らかに3独立変数の函数である。
r
A=――――――――X ……(r<>0)
1−(1+r)-N
A=X/N ……(r=0)
T=A・ N
また未知数をNやX,rとした場合も同様に3独立変数の函数であるが、rについては記述できる陽函数がないので直接計算できない。これを次節の「ニュートンの求根法」の例題に回して、NとXについて式を整理すると
利息がrの資金をX万円借りて毎年Aだけ返済する場合、何年(N)で払い終えるか!という問題と
r X
ln(1−―――)
A
N=−―――――――――――
ln(1+r)
利息がrの資金を借りてN年間にわたり毎年Aだけ返済する場合に、いったいX万円借りられるだろうか?
1−(1+r)-N
X=――――――――A……(r<>0)
r
X=N・A ……(r=0)
という3+1個の函数が考えられる。
XXXX DEF FNLOANX(A,N,R)=(1-EXP(-N*LOG(1+R)))*A/R と
XXXX DEF FNLOANA(X,N,R)=R/(1-EXP(-N*LOG(1+R)))/A
XXXX DEF FNLOANN(X,A,R)=-LOG(1-R*X/A)/LOG(1+R)
の三本が基本式であるが、R=0と R=A/Xが特異点になって別の式となり、R<0、または A/X<R は定義域外である。
事前チェックで定義域外を排除する必要がある。またr=0は別の式なので、区間切り替えスイッチを各項に設けて、ダミーを加えてエラーを回避して、値を求める。
4310 DEF FNLOANX(A,N,R)=
-(1-EXP(-N*LOG(1+FNNZ(R))))*A/FNNZ(R)*(R<>0)-N*A*(R=0)
4320 DEF FNLOANA(X,N,R)=
-X*FNNZ(R)/(1-EXP(-N*LOG(1+FNNZ(R))))*(R<>0)-X/N*(R=0)
4330 DEF FNLOANN(X,A,R)=
LOG(FNNZ(1-R*X/A))/LOG(1+FNNZ(R))*(R>0)-X/A*(R=0)
4350 DEF FNNZ(R)=R-(R=0)
実際にローンを組む場合には月払いとボーナス払いが入って、年利は複利でも年未満の計算が単利であり、更に実質金利を上げる拘束預金を要求されたりして複雑怪奇となって詳細の返済金額は金融機関の発行する標準返済表に頼ることがほとんどなのだけれど、基本の計算を自分の計算式で行なって返済計画のシミュレートをしておけば金融機関の出す条件の実質がかなりよく理解できて自信をもって交渉を進めることができる。
この純複利計算の式を金利の大きいサラ金・クレジット計算などに適用すると、返済行為毎の単利計算によって実質が複利となる民法491条の計算方式との誤差が目立ってくるから注意が必要だ。
何が主たる問題かの分析が大切
余談ついでに「トータル・システム」の観点から「パソコン用サラ金問題ソフト」を考えると、単に利息制限法などの基準を適用した債務明細確認・返済計画書を作るだけでは、どんなに高機能でスマートなソフトを作ったところで何等の被害者救済にはならないことを指摘したい。
サラ金クレジット問題をマクロでみれば、借金が美徳であるかの宣伝と次第に窮屈になる庶民生活といった社会状況の反映であることは疑いないが、ミクロで個々をみると、口を開けて待つ提灯あんこうの鼻先に自ら近づく小魚のような半ば病的な被害者がいるわけで、御当人が自覚的にその生き方を変える決意をしていないのに、周囲で勝手に解決してもほぼ100%再発してもとの黙阿弥となって解決に要したまわりの努力が水泡に帰すのが経験則である。債務そのものは特別の悪意がない限り「ない袖は振れない!」厳然たる事実の前に利息制限法だの債権○割カットだのとモットモらしい基準を設けて「返済計画書」をつくりその余を合法的に踏み倒していくプロセスが半ばルーチン化しているから、真面目に相談にのってくれる団体や弁護団の力添えを得れば技術的な困難はあまりないのであるが、当人が自分の責任として自覚して自立を図らない限り再発を防ぐ手はない。同級の友人で万年落選候補:BS………BS躍進区挑戦候補として生活相談所長をしている優秀な元技術者がいる。これが自作のサラ金ソフトで相談にのり「悪徳サラ金退治の名人」に祭り上げられて居るが、あまりに多い再発に呆れかえって対処方針を改め、少々解決金が高くなるにしても、当人の自覚を促すあれこれの措置に重点を移したのだった。債務確認・返済計画書などは詳細な作成法(操作法)を教えて、当人自身に作らせて、債権者との交渉も同席はするが必ず当人中心で進めさせることにした。相手方がにこやかに『この襟章は○○一家××組の幹部バッチなんです』などと「説明」するのを金看板の力で必死に聞き流して繰り返し説得して時には「国の未来を思う『憂国の士』が高利貸しの使い走りで庶民を苦しめるとは情けない!」などと一発かまして(恐縮して当りが柔らかくなったとか!)解決方法の同意を得るといったプロセスの中で被害者自身を教育して自立再起を促し、それでも立ち直り様のない場合には家族からの逆三下り半の作り方まで面倒を見ようという「トータル・プログラム」のなかでの補助ツールとして日常生活現場のパソコン・ソフトを考えるべきなのだ。これはカスタム・ソフトを請け負うシステム・ハウスの視点とはかなり違う「現場の視点」である。
また、この函数は貸借の立場を入れ替えると、そのまま年金計算になる。すなわち、信託銀行など金融機関にX万円を金利rで預けて、N年間にわたり年金を受け取る場合、毎年の年金額Aを求む!という風に利用できる。
さて、先のローン計算で、利率rがいくらになるかを算出してみよう。記述可能な逆函数が存在しない函数の根をコンピユータによる試行錯誤法を用いて力ずくで求めようというのだから、手計算での逆算は御免蒙るが、条件判断機能のあるプログラム電卓やコンピュータの演習課題としては丁度手頃の課題である。演算処理を抽象化する DEF FN には一層適する例題であり、このプログラムが正確に働くとビギナーは大変な自信がつく。
ただ、指摘して置くべきことがある。実際には記述できる逆函数が存在するのに試行錯誤法を用いるのは数学音痴がバレて公表用としてはかなり恥ずかしいからそこは慎重に調査し数学通に確認するなどした方が無難である。函数によってはかなり精度の高い近似式の存在が知られている。但し、ソフトは結果オーライの世界だから実務ツールとしては仕様を満足して正確に動けば中味は何でもよいというのも見識である。他人の評判のためにソフトを作るのではないのだ。言いたいヤツには言わしておこう(とでも言って居直っておかないと「数学苦手人間」の私にはウッカリ例を出せない!存在しないとは思うのだけれどもし万一逆函数があったらゴメンナサイ!)
ニュートンの求根法での函数利用
試行錯誤とはいっても、その対象によって様々な方法が考えられる。
@ 根の含まれている区間を何等分かして片端から値を計算して、正解に近い値の付近を更に細かく検索して指定誤差範囲内の解答を求める、という方法は複数の根を検索出来そうである。
A 根の値を推測して函数に当てはめて、その地点での接線方程式を求め、この接線の根を新たな根の推定値として、誤差の大きさを調べ、設定範囲より大きな誤差であれば、新たな推定値をもとに再度接線方程式の計算を繰り返すという方式が「ニュートンの求根法」と呼ばれて広く使われている。この方式は解への収束が早くてアルゴリズムも単純な一重ループなので重宝である。欠点は複数根を見逃してしまうことである。だから、原函数が単調で,単一根であることが分かっている場合に採用するとか、全定義域を分割して順番に根を求めて確認するなどの工夫のもとに採用できる方式である。この方式も高校生向けの数学の参考書に掲載されていた。最近の高校生は学ぶことが多くて大変だ。
:資料出所:
「電子計算機の数学」方程式の解法、ニュートンの逐次近似法、旺文社刊、新高数ゼミM、1975/7刊、竹之内脩著
函数g(x)の選定
ニュートン法ではまず題意から
g(x)=0 という方程式を準備してそのxiにおける接線方程式を求める。
xiでの接線の勾配を求めるにあたって微少な距離Δは、計算機で扱う都合上有限の値であるから、xiの両側に±Δ離れた点を設定して、傾きを求めた方が誤差が偏らない可能性が高い。
だから接線勾配は
d g(xi+Δ)-g(xi-Δ)
――{g(xi)}=――――――――――― α
dx 2 Δ
接線方程式は
g(x)−g(xi)= α(x−xi)
この方程式で g(x0)=0 の点x0が新たな根の推定値である。
.
x0−x1= −g(xi)/g(xi)
この式の値は推定根の修正値である。これが実用上充分小さければ推定作業を中止してよい。この判定を IF THEN で行なってリトライ・ループを形成して、根の収束をまつ訳である。
意外に簡単!ローン利息率計算への「ニュートン求根法」の適用
以上のアルゴリズムをローン計算で具体的に実現させてみよう。
前出の式を、2項の差を求める形に変形して g(x) とするが、Xが定数なので g(x)=FNLOANX(A,N,R)-X である。
5100 X=1000 '借入額(入力)
5110 A= 120 '毎期返済額(入力)
5120 N= 10 '返済期間(入力)
以上が題意の与値である。これより未知数の利息率rを求める。INPUTルーチンでこれらの値を与えると実用ラインとなる。使い勝手を考慮すると順不同で入力できることが望ましい。
5130 IF A*N<X THEN STOP '有り得ない数値のトラップ
5210 DEF FNLOANX(A,N,R)=
-(1-EXP(-N*LOG(1+FNNZ(R))))*A/FNNZ(R)*(R<>0)-N*A*(R=0) '原函数
5220 DEF FNNZ(R)=R-(R=0) '0→1
'
5240 DLT=0.00001 '微小変位幅を定義
5230 LIM=DLT '誤差限界を指定
利息率Rは、0より大きく、A÷Xよりも小さいのは確かであるから、
最初の推定値を仮に中間のA÷X÷2に選ぶ。
5300 R=A/X/2 '初推定値
'
5400 *RETRY '↓ Rの修正値船
5410 D=-2*DLT*(FNLOANX(A,N,R)-X)/(FNLOANX(A,N,R+DLT)-FNLOANX(A,N,R-DLT))
5420 R=R+D 'Rの新推定値
5430 IF ABS(D)>LMT THEN *RETRY '誤差範囲外は再計算
'
5500 PRINT "利息率は";USING "###.### [%]";R*100 '[[解答!!]]
5510 END
ニュートン法での実質演算部はわずか3〜4行に過ぎないが、数回の試行によってほんの一瞬で解答が獲られる。これをテストして身に付けておく価値は大きい。([LIST2]参照)
[4] LC共振周波数函数
コイル:Lとコンデンサー:Cによる共振回路の共振周波数:FRQは、概略
FRQ=1/(2×π×SQR(L×C))
であるから、これを定義すると
DEF FNFRQ(L,C)=1/SQR(L*C)/2/PAI#
となる。これは、更にL,Cをそれぞれ未知数とする函数に変形して使われることになる。
同調容量CAPは
DEF FNCAP(FR,L)=1/4/PAI#/PAI#/FR/FR/L
同調インダクタンスLIは
DEF FNLI(FR,C)=1/4/PAI#/PAI#/FR/FR/C
この手の函数は、CR発振機、ボーデ線図、ナイキスト線図、各種フィルターなど、設計関係の計算をほとんど網羅する。資料に書き貯めた計算式ほとんどそのままに DEF FN を冠すれば定義されてしまうのだから、こんな単純な話はない。
[5]
使える!汎用DEF FN 函数
最後にどこにでも使えそうなDEF FN函数を示しておく。通常の作業はこれまでに紹介した例とあわせこの程度のDEF FNのサンプルがあると、自分で独自に作成するDEF FNは主処理ルーチンなど極めて限られたものになり、大抵は文字列化など一部改造による流用や組合せの範囲で足りるはずである。
-a). 数値文字列の符号を取り払う
STR$(X)函数は符号の正負に拘らず1桁の符号桁をつける。符号桁を取り払いたい場合は、
@ 無条件に2桁目以降から抽出する。この場合、負符号も捨てられて符号は分からなくなる。
6010 DEF FNNS$(X)=MID$(STR$(X),2)
A 正と0の場合だけ2桁目から抽出し、負符号は残す。この型は負符号を利用する場合に採用する。
DEF FNNS$(X)=MID$(STR$(X),2+(X<0))
B 数値の定義域が0〜9の範囲に限られている場合は HEX$(X) をそのまま利用できる。
他の函数からも引用するから汎用性の点で@を採用するのが無難である。
-b). 数値の空白を指定文字列で埋める
整数値Xを右詰めN桁の文字列とし空白部を1バイト文字:B$で埋める函数は、先の符号桁を取る函数を引用して以下の様に定義する。
6020 DEF FNSTRB$(X,N,B$)=RIGHT$(STRING$(N,B$)+FNNS$(X),N)
-c). 数値の空白を'0'で埋める
整数値Xを右詰めN桁の文字列とし空白部を"0"で埋める函数は、先の空白を指定文字列で埋める函数を引用して以下の様に定義する。
6030 DEF FNSTRZ$(X,N)=FNSTRB$(X,N,"0")
-d). 符号付で空白をゼロで埋める
整数値Xを右詰めN+1桁の文字列として左端を符号とし空白部を"0"で埋める函数は、先の空白を"0"で埋める函数を引用して以下の様に定義する。
6040 DEF FNSTRSGN$(X,N)=MID$("+-",1-(X<0),1)+FNSTRZ$(X,N)
通信制御などでのフォーマットの苦労は激減する。
-e). 16進数値の空白を'0'で埋める
16進整数値Xを右詰めN桁の文字列とし空白部を"0"で埋める函数は以下の通りである。
6050 DEF FNHEXSTR$(X,N)=RIGHT$(STRING$(N,"0")+HEX$(X),N)
-f). 小数以下有効桁指定
小数部をN桁にまるめる函数は以下の通りである。
6060 DEF FNRND.$(X#,N)=FNNS$(INT(X#*10#^N+.5)/10#^N)
-g). 数値の書式指定汎用函数
数値の書式指定の汎用函数がないために、各人でいろいろ苦労している。なかには MS-DOSの RAMディスクを設定して、これを使って PRINT USING "#.##";X でシーケンシァル・ファイルに記録して、改めて文字列データとして読み出すという涙ぐましいプロセスで数値の書式指定文字列を作成される方もいる。これは配列データを多用しなければ不使用領域となる128[KB]を占有するだけで、バグのない書式指定ができるのだから RAMが640[KB]のシステムでは実用として妥当な方法というべきである。だが、ここはDEF FNの紹介のページであり、DEF FN 函数で構成してみよう。Xが数値、Nが全桁数、Mが小数部桁数とするとき、数値書式指定函数 FNRDX$(X,N,M) は以下の様に定義できる。定義に苦労しても、ライブラリーとなった後は中味に一切手を触れる必要がないのだからその後の効率はよい。
6070 DEF FNRDX$(X,N,M)=MID$(FNRDS$(X,N,M),INSTR(FNDP$(X),"."),N-(M>0))
6080 DEF FNDP$(X)
=MID$(STR$(X),2+(X<0))+STRING$(-(INSTR(STR$(X),".")=0),".")
(小数点付加 副函数)
6090 DEF FNRDS$(X,N,M)
=STRING$(N-M+(M=0)," ")+FNDP$(X)+STRING$(M,"0")
(左空白部付加 副函数)
【Y】 「汎用サブルーチン」vs 若干の優位「DEF FN函数」
「DEF FN方式」と「汎用サブルーチン方式」を比較せよ!と言われると、あれこれ付帯条件がついて説明にかなり苦労する。普通に使われるサブルーチンでは、変数名が特定されてしまうので、別のラインから同一の演算式であるが別の変数名の処理を呼ぶ場合に、前の値を一時退避させるなどの手続きが必要となり、更に値の受渡しが必要となるので、こうした状況では変数名を問わない DEF FNが優位なのは確かである。
-2). DEF FNで劇的な効果??
先日、手計算のアルゴリズムに忠実にコーディングして演算部が11頁に達するプログラムの機能追加改造を手掛けたが、テキストメモリーの空き容量が27バイトしかなかったので、全体の処理をコンピュータ向けの演算アルゴリズムに整理し直して DEF FN を採用した。結果は原アルゴリズムに見切りをつけるのが遅れたため新規製作同然になって計画日程に全く間に合わずに散々の目にあったが、内容としては演算部と函数定義部がそれぞれ1ページ前後の合計2ページ弱に整理されて、メモリー空き容量は10キロバイト余にも増え、追加要求仕様を総て実現させた上で全面削除されていたコメントを記載することができた。演算部ラインの原設計者はこれを「DEF FN の効用だ」と誤解した。だが実際は「手計算用の冗長度の高い(エラーを発見しやすい歴史的な)作業アルゴリズムをコンピュータ向けのアルゴリズムに変えたことが主因であり、汎用サブルーチン方式で作成したとしても4ページ程度に収まる内容だった。
-3). 「手計算そのまま」のオンメモリー化も妥当
このアルゴリズム変更は仕様追加変更を実現するために、小手先の処理では完全に行き詰まったメモリー不足の突破策としてやむなく行なったものであり、必然性もないのに正常に動作しているソフトの主要アルゴリズムを変更するのは、新たなバグ発生の機会を作るだけの愚行である。
必ずしもプログラミングの専門家ではない各種専門技術者が、日常手計算処理をそのままコーディングしてアルゴリズム・エラーが起こらないようにするという方針は実務家の方針としては極めて正しい方針である。手計算での共通重複部分をうまく整理統合できればなお良いのだが、慣れていて処理法に確信の持てるアルゴリズムを積極的に放棄する理由はない。それがどんなに歴史的・初等的アルゴリズムであっても要求仕様を満たして正確に動作する限りは妥当な方法である。これを非難しあるいは侮るというのは全くの筋違いである。とは言いながら、90゚までしか扱わない7桁対数表と計算尺時代のアルゴリズムそのままの膨大なラインと格闘して、思わず酔いの勢いで「測量屋さんてぇのは幾何学の世界なんですねェ」と呟いてしまったのだ。「電気屋さんにとっては三角函数は『波』だから連続してるけど、土木建築は作図で考えるから90゜以上の角度は眼中になかったですヨ」と応じてきたが、まさにその通りで私の失言であった。PC-98の能力の限界に挑戦する要求仕様がなければ全く問題にもならず正常に演算していたのだから。
-4). 結局はアルゴリズムに帰着
ところが、サブルーチンをDEF FNの機能に倣って一般化して、「ローカル変数」を定めて、これに代入する形で処理を進める形式に整理すると、変数同士の衝突の問題と、サブルーチンへの入出力でのデータの受渡し手続きとテキストメモリー消費の問題に集約される。同じアルゴリズムを採用する場合にはDEF FNが若干優位だとしても、テキスト・メモリーが足らないなどの物理的な制限にぶつかって「劇的効果」を挙げるまでは、決定的に優位になることはあまりないのである。
-5). 「たたき大工」方式では
サブルーチンの定義は自由度が高いから、その場その場に合わせて作ってしまう。そのあとから微妙に違う処理が出て来ると共通化が難しくなる。
DEF FN函数定義では一般化を含む詳細の検討を求められるから、その検討の中でアルゴリズム自体が改良されて、その効果の方が実は大きいのだろう。
結局、丁寧に設計したプログラムではDEF FNとサブルーチンの大差は生まれないが、解析の手を抜いて現物合わせ方式になるに従って、手抜きをあまり許さないDEF FNの方がその分品質が落ちにくくなるというのが正確な実態だろう。
解析での手抜きは、不具合対策などでその何倍もの労力を奪う。その意味では一応の解析を要求する DEF FN函数の方に(理論上よりも)実務上の長所があると言えるのだろうか。
多くのポケコンやプロセス制御用に開発されたコンテックのPC−98用リアルタイムBASICなどにはDEF FN函数機能は存在しない。そこでは、「ローカル変数」を定めて、他の変数との衝突に注意しながら DEF FN並のエネルギーを費やした汎用サブルーチンを作成してDEF FNに代えるほかないのである。
少なくない方がDEF FNについて重大な誤解をしている。『DEF FN定義式は、どのように複雑なものとなってもしかたない。一種のブラックボックスとなる。函数定義式は1行の範囲に限られる………。FOR文,IF文を用いれば簡単に表現できることが複雑な式となったり,ときには記述不可となったりする。………分かりづらくなることは避けられないようだ。』(前出「プログラミング・スタイルブック」)。
これはユーザーが定義したDEF FN函数を、別のDEF FN函数から何重にも参照できることを見落としたため発生した誤解だと思う。本稿を振り返ってみても、引例の多くは多重引用によって式を定義している。個々の機能式や区間式はそんなに複雑怪奇ではないし、全定義域を表わす式も各区間式の和だから形態は単純で見通しは悪くないはずである。函数の内容自体が複雑だというのはDEF FNに限らず何を使っても複雑になるから本来別問題である。
函数の定義域中の区間の切り替えに用いている「論理式の値」が分かりにくい最大の理由は「あまり使い慣れていないこと」に加えて'真'の場合に'−1'を示すことではないだろうか。そうした誤解に基づく否定的評価は修正されるべきである。
-2). DEF FNエキスパート ≠ 一人前プログラマー
ここまで読んで理解できた方は DEF FNに関しては間違いなくエキスパートになっている。いままで DEF FN について系統的に整理したものがなかったのだから、若干複雑な函数やアルゴリズムを読み飛ばしたところでたいした違いはない。記述内容は中学から高校1年2年程度の水準でも教科書・参考書と突合せながら読みこなせる内容である。だが、三十路も半ばを過ぎるとかっての輝ける頭脳もいい加減腐れはじめてトンカチでブッたたいて詰め込む尻から溢れていく甚だ情けない状態となる。「The BASIC」誌は「日経パソコン」と並んで読者のオジさん度の高い雑誌と聞いている(不惑も半ばで両方読んでる私は'KYON2'ならぬ'OJIN2'なのか?!)。だから、若い頭よりかなり理解力の落ちたオジさん読者のうち1割の読者が新たに読みこなして理解したとしても一挙に公称万人台の「DEF FN 函数のエキスパート」が誕生する訳である。だがプログラマーとしての実力はそれとは独立のことがらである。システムを構成する能力やアルゴリズムを構成する能力、組織を管理し掌握する能力などが DEF FNを知ったからといって飛躍的に向上する訳ではない。コーディングに当たって DEF FN函数を気楽に、以前より適切に使えるようになっただけであるから半人前はやはりまだ半人前である。先出の「スタイルブック」は概ね良く整理されていて参考に値する本だから改訂版がでる可能性があるが、そのとき、「DEF FN使用=一人前説」を書いた著者氏は DEF FN について何と書き改めるのか、いささか楽しみではある。「袋ファイル……整理学」なんて前に、ソフトのプロからみた「DEF FN整理学」なんてのが出されていれば、プログラマーとしては半人前:アマチュア:の(別分野が専門である)私がシコシコまとめることはなかったのである。
-3). 各分野の専門家の方が
とはいうけれど本当は、DEF FN函数というのは、プログラム専門の人たちよりもむしろ、専門分野にはエキスパートで、その分野での処理のためにプログラムを作る人たちにこそ向いている。専門技術者は固有技術の集約である諸函数を元々持っているのだから、極値処理と定義域の検討はやり直したとしても基本的に DEF FNを冠するだけで「高度の技術的内容をもった DEF FN函数」がぞろぞろ出てくるはずである。そうした専門分野の人が常用する DEF FNを一般形に整理して理論化した方が内容の濃いものになるのだろうか。ソフトが本職だったようにお見受けする著者氏に「ソフト開発専業ではない一般現場向けのDEF FN論」を期待しても気の毒なのかもしれない。本稿での演習にまる一日もかければ、もうあなたは DEF FN自体で困ることはない。
DEF FNサンプル・ライン
以上の解説に用いたプログラム・ラインをサブルーチンにして[リストT]に示す。
GOSUB *DEF.FN [[Enter]]
とした後は、ダイレクト・モードでも使用できるので気軽にテストして頂きたい。必要部分を取り出してリナンバーしたものをアスキーセーブすればマージ用のライブラリーそのものである。
「ニュートンの求根法」の例は[リストU]に、各DEF FN函数の使用サンプルは[リストV]に示す。[リストV]では、次項へは'ヒ'キー、',,'、または画面の指示により'ESC'キーを用いる。三角形の計算では不合理値排除がないのでIllegal fanction callで停止する。一旦できたDEF FNのその後の使用は実にあっけないのだ。
-4). DEF FN函数演習2
最後に、DEF FN演習問題をひとつ。
- @ 点Pから点ABを通る直線へおろした垂線の足Tの座標を求む。
- A 算出不能領域の措置について述べよ。
- B 算式の自動切り替え法は?
- C TがABの区間内かどうか判定せよ
- D 直線ABから点Pまでの距離は?
- E 上記の距離について、左回転方向を正とする符号を付けよ
以上の問題は現役高校生なら一日もあれば全部解けるだろうが'OJIN族'ではかなりのバラつきが出るだろう。私は解析に3日もかかってしまった。「座標と方程式」なんてのが日常業務に出現したことはなかったとはいえ歳だねェ。実用では特殊分野の利用に限られ若干趣味的に見えるので解法は省略するが極値処理を解決できれば頭の体操としてはさして高級な問題ではない。だが、いきなりパソコンに向かったところで解ける問題ではないことは保証する。
今後、読者諸兄作成のユニークなDEF FN函数作成のレポートを期待したい。
1000 *DEF.FN 'DEF FN函数定義 サンプルライン
1100 ' save "DEF_FNSM.BAS",A'
1110 '「機能番号」よりも「名称」が楽:独立変数のない函数
1120 ' 直近に処理したグラフィック座標(GX,GY)
1130 DEF FNGX=POINT(0)
1140 DEF FNGY=POINT(1)
1150 ' スクリーン座標(SX,SY)
1160 DEF FNSX=POINT(2)
1170 DEF FNSY=POINT(3)
1180 '
1190 '
1200 ' 倍精度VAL ↓ 小数点付加 ↓ 0 付加
1210 DEF FNVALU#(D$)=VAL(D$+FNVALDP$(D$)+STRING$(8,"0"))
1220 DEF FNVALDP$(D$)=STRING$(-(INSTR(D$,".")=0 OR INSTR(D$,"E-")>0),".")
1300 ' 倍精度CDBL
1310 DEF FNCVDBL#(X)=FNVALU#(STR$(X))
1320 '
1400 ' 小数部函数
1410 DEF FNFRC(X)=BRW#*X-FIX(BRW#*X)
1420 BRW#=1.00000006#
1430 '
1500 '「論理式の値」の利用
1510 ' 大きい数を選択する函数
1520 DEF FNMAX(X,Y)=-X*(X>=Y)-Y*(X<Y)
1525 '小さい数を選択する函数
1530 DEF FNMIN(X,Y)=-X*(X<=Y)-Y*(X>Y)
1540 '
1600 ' X$が数字であることの判定
1610 DEF FNNUMS(X$) ="0"<=X$ AND X$1620 '
1630 ' X$が数字であり、小数点の場合は付加されるE$内に小数点不存在の判定
1640 DEF FNNUM(X$,E$)=FNNUMS(X$) OR X$="." AND INSTR(E$,".")=0
1650 '
1700 ' X$がアルファベットであることの判定
1710 DEF FNALP(X$) =("A"<=X$ AND X$<="Z") OR ("a"<=X$ AND X$<="z")
1720 '
1800 ' X$が数値かアルファベットである判定
1810 DEF FNALNUM(X$) =FNNUMS(X$) OR FNALP(X$)
1820 '
1900 ' X$が16進数であることの判定
1910 DEF FNHEXA(X$) ="A"<=X$ AND X$<="F" OR "a"<=X$ AND X$<="f" OR FNNUMS(X$)
1920 '
2000 ' 数値が値域外であることの判定←(処理の明確化)
2010 DEF FNOUTX(X,H,L)=X>H OR L>X
2020 '
2100 ' PC-9801でシフトキーが押されていることの判定
2110 DEF FNSHIFT=(INP(&HE8) AND &H40)=0
2120 '
2410 PAI#=3.141592653589793#+2.385D-16
2420 RAD#=PAI#/180#
2430 DEG#=180/PAI#
2440 '
2600 'アーク・サイン/アーク・コサイン
2610 DEF FNACSIN#(X)=-ATN(X/SQR(1#-X*X-(X*X=1)))*(X*X<>1)-SGN(X)*(X*X=1)*PAI#/2
2620 DEF FNACCOS#(X)=-ATN(SQR(1#-X*X)/(X+(X=0)))*(X*X<>0)-(X=0)*PAI#/2
2630 '
2790 '値域の変換
2800 '方位角を±πに変換する函数
2810 DEF FNPNTH#(X#)=X#+2*PAI#*(X#>PAI#)
2820 '±πを方位角に変換する函数
2830 DEF FNDRC#(X#)=X#-2*PAI#*(X#<0)
2840 '
2890 '角度の加算/減算
2900 '角度の値域を方位角(0〜2π:360度)とする加算/減算
2910 DEF FNADDDR#(A#,B#)=A#+B#+2*PAI#*(A#+B#>2*PAI#)
2920 DEF FNSUBDR#(A#,B#)=A#-B#-2*PAI#*(A#2930 '角度の値域を±π(±180度)とする加算/減算
2940 DEF FNADDTH#(A#,B#)=A#+B#+2*PAI#*(A#+B#>PAI#)
2950 DEF FNSUBTH#(A#,B#)=A#-B#-2*PAI#*(B#-A#>PAI#)
2960 '
3000 '全方位アーク・タンジェント
3010 DEF FNACTN#(X,Y) =FNR0#(X,Y)+FNR1#(X,Y)+(-(X<0)+(X>0)*(Y<0)*2)*PAI#
3020 DEF FNR0#(X,Y) =(.5-(Y<0))*PAI#*(X=0)*(Y<>0) 'X=0:±90゚
3030 DEF FNR1#(X#,Y#)=-ATN(Y#/(X#+(X#=0)))*(X#<>0) '[ X<>0 ]
3040 'PAI#=3.141592653589793#+2.385D-16' (20桁)
3050 '
3200 '直交座標の回転変換
3210 DEF FNROTX(X,Y,TH)=X*COS(TH)-Y*SIN(TH)
3220 DEF FNROTY(X,Y,TH)=X*SIN(TH)+Y*COS(TH)
3230 '
3490 '度分秒←→度、相互変換
3500 ' DDD.MMSS → DDD.DDDD 変換
3510 DEF FNDEG#(X#)=FNDMI(X#)+FNDF1#(X#)+FNDF2#(X#) 'DMS$-->DEG
3520 DEF FNDMI(X) =FIX(X*BRW#):BRW#=1.00000006# '整数部
3530 DEF FNDF1#(X#)=FIX(FNFRC(X#)*100)/60#
3540 DEF FNDF2#(X#)=FIX((FNFRC(X#)*100-FNDF1#(X#)*60)*100+SGN(X#)/2)/3600#
3550 '
3600 ' DDD.DDDDDD → DDD.MMSS 変換
3610 DEF FNDMS#(X#)=FIX(X#)+FNMM#(X#)/100#+FNSS#(X#)/10000#
3620 DEF FNMM#(X#)=FIX(FNFRC(X#)*60)
3630 DEF FNSS#(X#)=FIX((FNFRC(X#)*60-FNMM#(X#))*60+SGN(X#)/2)
3640 '
3990 '正弦法則
4000 '二辺と片頂角から他頂角 [ B>=A*SIN(TH) ]
4010 DEF FNSINANG(A,B,TH)=FNACSIN#(A*SIN(TH)/B)
4020 '
4050 '一辺と両端頂角から他の辺
4060 DEF FNSINLEN(C,TA,TB)=C*SIN(TA)/SIN(PAI#-TA-TB)
4070 '
4090 '余弦法則
4100 '二辺と挟角から一辺
4110 DEF FNCOSLEN(A,B,TH)=SQR(A*A+B*B-2*A*B*COS(TH))
4140 '三辺から一角を求む
4150 DEF FNCOSANG(A,B,C)=FNACSIN#(SQR(1-((A*A+B*B-C*C)/A/B/2)^2))
4160 '
4170 '(余弦法則+正弦法則)
4180 '二辺とその挟角から他の一角をを求む
4190 DEF FNTRITH(A,B,TH)=FNSINANG(A,FNCOSLEN(A,B,TH),TH)
4200 '
4210 '三辺から面積を求む1
4220 DEF FNSINC(a,b,c)=sqr(1-((c^2-a^2-b^2)/a/b/2)^2)
4230 DEF FNSQUARE(a,b,c)=a*b*FNSINC(a,b,c)/2
4240 '
4250 '三辺から面積を求む2、ヘロンの公式
4260 DEF FNS(a,b,c)=(a+b+c)/2
4270 DEF FNSQUAREH(a,b,c)
=sqr(FNS(a,b,c)*(FNS(a,b,c)-a)*(FNS(a,b,c)-b)*(FNS(a,b,c)-c))
4280 '
4290 '
4300 'ローン/年金原価計算
4310 DEF FNLOANX(A,N,R)=-(1-EXP(-N*LOG(1+FNNZ(R))))*A/FNNZ(R)*(R<>0)-N*A*(R=0)
4320 DEF FNLOANA(X,N,R)=-X*FNNZ(R)/(1-EXP(-N*LOG(1+FNNZ(R))))*(R<>0)-X/N*(R=0)
4330 DEF FNLOANN(X,A,R)=LOG(FNNZ(1-R*X/A))/LOG(1+FNNZ(R))*(R>0)-X/A*(R=0)
4350 DEF FNNZ(R)=R-(R=0)
4360 '
5990 '汎用DEF FN函数
6000 '数値文字列の符号桁を取り去る
6010 DEF FNNS$(X)=MID$(STR$(X),2)
6020 '数値の空白を指定文字列で埋める
6030 DEF FNSTRB$(X,N,B$) =RIGHT$(STRING$(N,B$)+FNNS$(X),N)
6040 '数値の空白を'0'で埋める
6050 DEF FNSTRZ$(X,N)=FNSTRB$(X,N,"0")
6060 '符号付で空白をゼロで埋める
6070 DEF FNSTRSGN$(X,N)=MID$("+-",1-(X<0),1)+FNSTRZ$(X,N)
6080 '16進数値の空白を'0'で埋める
6090 DEF FNHEXSTR$(X,N)=RIGHT$(STRING$(N,"0")+HEX$(X),N)
6100 '小数部をN桁にまるめる函数は以下の通りである。
6110 DEF FNRND.$(X#,N)=FNNS$(INT(X#*10#^N+.5)/10#^N)
6120 '数値の書式指定の汎用函数
6130 DEF FNRDX$(X,N,M)=MID$(FNRDS$(X,N,M),INSTR(FNDP$(X),"."),N-(M>0))
6140 DEF FNDP$(X) =MID$(STR$(X),2+(X<0))+STRING$(-(INSTR(STR$(X),".")=0),".")
6150 '(小数点付加 副函数)
6160 DEF FNRDS$(X,N,M)=STRING$(N-M+(M=0)," ")+FNDP$(X)+STRING$(M,"0")
6170 '(左空白部付加 副函数)
6180 '
6190 '
7000 '演習サンプル
7010 '一次函数は三独立変数函数!
7020 DEF FNF(X,A,B)=A*X+B' ……(A<>0)
7030 '
7040 '二次函数は四独立変数函数!
7050 DEF FNF2(X,A,B,C)=A*X*X+B*X+C
7060 '
7500 'LC共振の定数計算
7510 '共振周波数
7520 DEF FNFRQ(L,C)=1/SQR(L*C)/2/PAI#
7530 '同調容量CAP
7540 DEF FNCAP(FR,L)=1/4/PAI#/PAI#/FR/FR/L
7550 '同調インダクタンスLIは
7560 DEF FNLI(FR,C)=1/4/PAI#/PAI#/FR/FR/C
7570 '
8000 RETURN
5000 ' save "NEUTON.BAS",A' ニュートンの求根法の実験
5100 X=1000 '借入額(入力)
5110 A= 120 '毎期返済額(入力)
5120 N= 10 '返済期間(入力)
5130 '
5140 IF A*N5200 DEF FNLOANX(A,N,R)=FNLNX1(A,N,R)+FNLNX0(A,N,R)
5210 DEF FNLNX1(A,N,R)=-(1-EXP(-N*LOG(1+FNNZ(R))))*A/FNNZ(R)*(R<>0)
5220 DEF FNLNX0(A,N,R)=-N*A*(R=0) 'R=0
5230 DEF FNNZ(R)=R-(R=0) '0→1
5240 DLT=.00001 '微小変位幅を定義
5250 LMT=DLT '誤差限界を指定
5260 '
5300 R=A/X/2 '初推定値
5310 '
5400 *RETRY
5410 D=-2*DLT*(FNLOANX(A,N,R)-X)/(FNLOANX(A,N,R+DLT)-FNLOANX(A,N,R-DLT))
5420 R=R+D
5430 IF ABS(D)>LMT THEN *RETRY '誤差範囲外は再計算
5440 '
5500 PRINT "利息率は";USING "###.### [%]";R*100 '[[解答!!]]
5510 END
100 ' save "DEF_FNTS.BAS",A' DEF FN函数 利用TEST
110 ' 1990/07/31,/08/11 T.Mizushima
120 WIDTH 80,25:CONSOLE 0,25,,1:SCREEN 3,0,0,1:COLOR 5,0,0,0,1
130 Q$=CHR$(27):QT$=CHR$(34)
140 GOSUB *DEF.FN 'DEF FN函数のセット
150 '
160 PRINT "Please hit Return Key."
170 '
180 WINDOW(0,0)-(1280,800) : LINE(639,0)-( 0,399),7
185 PRINT "(GX,GY)=("FNGX","FNGY"), (SX,SY)=("FNSX","FNSY") ";:INPUT "",X$
190 '
200 X$=" ":PRINT "倍精度VAL/CDBL函数"
210 WHILE X$>"":INPUT "Number X$=",X$:X#=VAL(X$)
220 PRINT "X$="X$", X#=VAL(X$)="X#", FNVALU#(X$)="FNVALU#(X$);
230 PRINT ", CVDBL#(X)="FNCVDBL#(VAL(X$))
240 WEND
250 '
260 X=1.234:PRINT "小数部:FRAC(X)函数 と MAX & MIN"
270 WHILE X<>0:INPUT " X=",X
280 PRINT "FRC(1000*FRC(X))="FNFRC(FNFRC(X)*1000),
290 PRINT "MAX(X,PAI#)="FNMAX(X,PAI#);
300 PRINT "MIN(X,PAI#)="FNMIN(X,PAI#)
310 WEND
320 '
330 '
340 X$=" " : PRINT "キー入力判定:ESC=EXIT"
350 F$="ASC=&&_, Num=##_, Alp=##_, Alp or Num=##_, HEXA=##_, "
360 WHILE X$<>Q$:PRINT " X$=";:X$=INPUT$(1)
370 PRINT USING F$;HEX$(ASC(X$)),FNNUMS(X$),FNALP(X$),FNALNUM(X$),FNHEXA(X$);
375 PRINT USING "Shift=##";FNSHIFT
380 WEND
390 '
400 PRINT "逆三角函数と値域変換":X=-1
410 WHILE X<>0:INPUT "±三角比X=",X
420 PRINT "ARCSIN(X)="FNACSIN#(X)*DEG#,FNDRC#(FNACSIN#(X))*DEG#
430 PRINT "ARCCOS(X)="FNACCOS#(X)*DEG#,FNDRC#(FNACCOS#(X))*DEG#
440 WEND
450 '
460 '
470 PRINT "角度の加算/減算:":X=45
480 WHILE X<>0 OR Y<>0:INPUT "X±Y:(X,Y)=",X,Y
490 PRINT "(X+Y)="FNADDDR#(X*RAD#,Y*RAD#)*DEG#,FNADDTH#(X*RAD#,Y*RAD#)*DEG#
500 PRINT "(X-Y)="FNSUBDR#(X*RAD#,Y*RAD#)*DEG#,FNSUBTH#(X*RAD#,Y*RAD#)*DEG#
510 WEND
520 '
530 '
540 PRINT "全方位アークタンジェント(Y/X)":X=-1
550 WHILE X<>0 OR Y<>0:INPUT "±動径(X,Y)=",X,Y
560 PRINT "ARCTAN(Y/X)="FNACTN#(X,Y)*DEG#,FNPNTH#(FNACTN#(X,Y))*DEG#
570 WEND
580 '
590 PRINT CHR$(12)"座標の回転変換 DEMO";
595 X0=320 : Y0=200 : RD= 100 : WINDOW(0,0)-( 639,399)
600 FOR DF =0 TO 1:DRC=1-2*DF
605 FOR TH=0 TO PAI#*2-.01 STEP PAI#/12#
610 RESTORE 590
615 READ X$,Y$ : GOSUB *P.CAL
620 PSET(XD,YD) : COL=(RND(TH)*8 MOD 7)+1
625 WHILE X$<>"END"
630 LINE-(XD,YD),COL
635 READ X$,Y$ :GOSUB *P.CAL
640 WEND
645 NEXT TH
650 NEXT DF
655 X$=INPUT$(1):CLS 3:IF X$<>CHR$(13) THEN 590 ELSE 700
660 DATA 0,+10, 20,+10, 25, 0, 20,-10, 0,-10, 0,+10, END,END
665 '
670 *P.CAL
675 XD=X0+RD*(1+DF/2.5)*COS(TH*DRC)+FNROTX(VAL(X$),VAL(Y$),TH*DRC)
680 YD=Y0+RD*(1+DF/ 4 )*SIN(TH*DRC)+FNROTY(VAL(X$),VAL(Y$),TH*DRC)
685 RETURN
690 '
700 PRINT "度分秒→度→度分秒、相互変換":D=-1
710 WHILE D<>0 : INPUT "±度.分秒=",D
720 PRINT "ANGLE="FNDEG#(D)"[Deg], ="FNDMS#(FNDEG#(D))"[DD.MMSS]"
730 WEND
740 '
750 N=10 : B$="*" : M=4 : X=-1
760 PRINT "数値文字列の加工:(桁数="N", 小数部="M", 埋め文字列="B$")"
770 F$="No SGN=@_, B$=@_, 00=@_, +0=@_, ": G$="HEXA=@_, Round=@_, Rnd2=@"
780 WHILE X<>0 : INPUT " X=",X
790 PRINT USING F$;FNNS$(X),FNSTRB$(X,N,B$),FNSTRZ$(X,N),FNSTRSGN$(X,N);
800 PRINT USING G$;FNHEXSTR$(FNMIN(X,65535!),4),FNRND.$(X,N),FNRDX$(X,N,M)
810 WEND
820 '
830 PRINT "二辺と一頂角→二頂角と一辺": A=45
840 WHILE A<>0 OR B<>0 :INPUT "(a,b,θB)=",A,B,TH:IF A+B+C=0 THEN 880
850 PRINT "θA="FNSINANG(A,B,TH*RAD#)*DEG#;
860 PRINT ", θC="180-TH-FNSINANG(A,B,TH*RAD#)*DEG#;
870 PRINT ", 辺c="FNCOSLEN(A,B,PAI#-TH*RAD#-FNSINANG(A,B,TH*RAD#))
880 WEND
890 '
900 PRINT "一辺と二頂角→二辺と一頂角": TA=45
910 WHILE TA<>0 OR TB<>0 :INPUT "(c,θA,θB)=",C,TA,TB:IF TA+TB=0 THEN 950
920 PRINT "θC="180-TA-TB;
930 PRINT ", 辺a="FNSINLEN(C,TA*RAD#,TB*RAD#);
940 PRINT ", 辺c="FNSINLEN(C,TB*RAD#,TA*RAD#)
950 WEND
960 '
970 END : GOTO 100
980 '---------------------------
990 '