Gforth (および標準Forth) は、単純な数値表示(. や f.)だけでなく、出力を任意に制御するための強力で柔軟な仕組みを提供する。これは、C言語の printf のような単一の多機能コマンドに依存するのではなく、Forthの哲学である「小さな部品(ワード)を組み合わせてより大きな機能を作る」ことを体現するものである。
Gforthの数値書式設定は、大きく分けて以下の2つのシステムに基づいている。
-
Pictured Numeric Output (整数描画):
<#で始まり#>で終わる、整数(倍長整数)を対象としたシステムである。1桁ずつ、あるいはまとめて文字列に変換し、その途中で任意の文字(,や$など)を挿入できる。文字列を右から左へ(下位桁から)構築するのが最大の特徴である。 -
Floating-Point Formatting (浮動小数点):
f.rdpファミリ(f>str-rdp,f>buf-rdp)を使用するシステムである。フィールド幅、小数点以下の桁数、形式(固定小数点/指数)を指定し、FPUスタック上の数値を一括で文字列に変換する。
Pictured Numeric Output (整数の書式設定)
これはForthにおける伝統的かつ強力な整数書式設定の仕組みである。1,234 や (456)(会計形式)のような、標準の . では不可能な出力を実現するために使われる。
1. 3つの基本ルール
この仕組みを理解する上で、まず把握すべき3つの重要なルールが存在する。
-
常に「倍長整数 (Double)」で動作する。
スタック上の数値はud(符号なし倍長整数)であると期待される。シングル整数nを変換したい場合は、S>D(符号あり倍長dに変換)や0(符号なし倍長udに変換)を使って事前に変換する必要がある。 -
常に「符号なし (Unsigned)」として扱われる。
#や#Sなどの変換ワードは、スタック上の数値が負であるかどうかを考慮しない。負の数を扱うには、後述するSIGNワードと絶対値(DABS)を組み合わせる必要がある。 -
文字列は「右から左へ」構築される。
これが最も重要な特徴である。123を変換する場合、内部のバッファには'3'、次に'2'、次に'1'の順で書き込まれる。
2. 主要なワード
これらのワードは、<# と #>(またはGforthの <<# と #>>)の間で使用される。
| ワード | スタック (S:) | 機能 |
|---|---|---|
<# |
( -- ) |
**変換開始。**内部の数値描画バッファを初期化する。 |
# |
( ud1 -- ud2 ) |
1桁だけ変換。 ud1 を BASE(基数)で割り、余り(最下位桁)を文字に変換してバッファに挿入。商 ud2 をスタックに残す。 |
#S |
( ud -- 0 0 ) |
残り全てを変換。 ud が 0 になるまで # を自動で繰り返す。ud が 0 でも最低1桁の "0" を出力する。 |
HOLD |
( char -- ) |
1文字挿入。 char を現在のバッファ位置に挿入する。 |
SIGN |
( n -- ) |
符号処理。 シングル整数 n が負の場合のみ、-(マイナス記号)をバッファに挿入する。 |
#> |
( d -- c-addr u ) |
変換終了。 スタック上の d(通常は #S が残した 0 0)を捨て、完成した文字列のアドレス c-addr と長さ u を返す。 |
<<# |
( -- ) |
Gforth固有。<# と同様に開始するが、ネスト可能な専用領域を使う。 |
#>> |
( -- ) |
Gforth固有。<<# の領域を解放する。#> で文字列取得後に呼び出す。 |
Gforthでは、<# / #> よりも <<# / #>> のペアが安全な作法として推奨されることが多い。
3. 実践的な使用例
最も単純な例である。#S ですべての桁を変換する。
: my-u. ( u -- )
0 \ ( u 0 ) -- シングル(u)を符号なし倍長(ud)に変換
<<# \ ( ud ) -- 変換開始
#S \ ( 0 0 ) -- 数値が0になるまで全ての桁を変換
#> \ ( c-addr u ) -- 変換終了。文字列のアドレスと長さを得る
TYPE \ "123" などを画面に表示
SPACE
#>> \ ホールドエリアを解放
;
123 my-u. \ 実行結果: 123
# を使って1桁ずつ処理を止め、HOLD で小数点を挿入する。 (1234 -> "12.34")
: dollars-and-cents ( u -- )
0 \ ( 1234 0 ) -- 符号なし倍長(ud)に
<<#
\ --- 小数点以下2桁 ---
# \ ( 123 0 ) -- '4' を変換
# \ ( 12 0 ) -- '3' を変換 (バッファ内: "34")
\ --- 小数点を挿入 ---
[CHAR] . HOLD \ ( 12 0 ) -- '.' を挿入 (バッファ内: ".34")
\ --- 整数部(残り全部)---
#S \ ( 0 0 ) -- 残り '12' を一気に変換 (バッファ内: "12.34")
[CHAR] $ HOLD \ ( 0 0 ) -- Gforthマニュアルの例に倣い '$' も追加 (バッファ内: "$12.34")
#>
TYPE SPACE
#>>
;
1234 dollars-and-cents \ 実行結果: $12.34
5 dollars-and-cents \ 実行結果: $0.05
負の数を扱うには、S>D で符号あり倍長 d にし、SIGN 用のフラグ n と DABS で絶対値 ud に分離するのが定石である。
: my-. ( n -- )
S>D \ ( d ) -- 符号ありシングル(n)を符号あり倍長(d)に
\ 例: n=-123 -> d=(-123 -1)
\ SIGNは n を、#Sは ud を必要とするため、スタック操作で分離
SWAP OVER \ ( n d ) -> ( d n ) -> ( d n d )
DABS \ ( d n ud ) -- dの絶対値udを計算
\ スタック: ( d n ud )
\ 例 n=-123: (-123 -1) → -1 (123 0)
<<# \ 変換開始
#S \ ud (123 0) を全て変換 (バッファ内: "123")
ROT SIGN \ 符号
#> \ ( c-addr u ) -- 変換終了
TYPE SPACE
#>> \ ホールドエリアを解放
;
-123 my-. \ 実行結果: -123
456 my-. \ 実行結果: 456
# とループを組み合わせることで、より複雑な書式が可能である。
: format-commas ( ud -- 0 0 )
BEGIN
# # # \ 3桁ずつ変換
2DUP D0= 0= IF \ ( ud ud ) まだ残りの桁(ud)があるか?
WHILE
[CHAR] , HOLD \ あればカンマを挿入
REPEAT
DROP
#S \ 最後の3桁(またはそれ以下)を変換
;
: n, ( n -- )
S>D SWAP OVER DABS \ my-. と同じ準備
<<#
format-commas \ 3桁区切りで変換
ROT SIGN \ 符号処理
#> TYPE SPACE
#>>
;
1234567 n, \ 実行結果: 1,234,567
-10000 n, \ 実行結果: -10,000
浮動小数点数の書式設定
浮動小数点数(FPUスタック上の rf)の書式設定は、整数描画とは全く異なるシステムを使用する。f.rdp とその派生ワード群は、C言語の printf の %.nf や %.ne に似た、より高レベルな機能を提供する。
1. 共通の書式指定パラメータ
f.rdp, f>str-rdp, f>buf-rdp の3ワードは、整数スタック(S:)上で共通の3つのパラメータ(+nr +nd +np)を使って書式を指定する。
- +nr (Right): 出力の総フィールド幅。変換後の文字列はこの幅で右寄せされる。
- +nd (Digits): 固定小数点表記が選択された場合の、小数点以下の桁数。
- +np (Precision/Minimum): 最低有効桁数
指定された値で表示できない場合は、指数表示となる。
12345.6789e 10 2 7 F.RDP \ → " 12345.68"
12345.6789e 10 2 8 F.RDP \ → "1.234568E4"
2. 用途別:3つの主要ワード
これら3つのワードの最大の違いは、変換した文字列が「どこに」出力されるかである。
PI \ 3.14159e
10 2 0 \ ( S: 10 2 0 ) -- 幅10, 小数点以下2桁, 最低有効桁数 0
F.RDP \ 画面に " 3.14" と表示
FPUスタックの rf を、指定書式で現在の出力(通常は画面)に直接表示する。
PI \ 3.14159e
15 2 0 \ 幅15, 小数点以下2桁, 最低有効桁数 0
F>STR-RDP TYPE \ 3.14
指定書式で文字列に変換し、Gforthの一時的な数値描画バッファ(<# と共通)に格納する。その文字列のアドレス c-addr と長さ u を整数スタックに返す。
注意: このバッファは揮発性である。他の数値変換ワード(<# や . など)を呼び出すと、内容は即座に上書きされる。F>STR-RDP TYPE の様に、続けて記述するほうがよい。
\ 50バイトのバッファを "my-f-string" という名前で確保
CREATE my-f-string 50 ALLOT
pi \ ( F: 3.14159... )
my-f-string \ ( S: c-addr ) -- 保存先バッファのアドレス
10 4 0 \ ( S: c-addr 10 4 0 ) -- 書式を指定
f>buf-rdp \ ( S: ) -- my-f-string に " 3.1416" が書き込まれる
\ 確認: my-f-string の中身を表示
my-f-string 10 TYPE CR \ " 3.1416" と表示される
指定書式で文字列に変換し、プログラマが**指定した c-addr(バッファの先頭アドレス)**に直接書き込む。変換した文字列を永続的に保存・再利用したい場合。最も安全で柔軟な方法である。c-addr には、+nr で指定した幅の文字列を格納するのに十分な領域を確保しておく必要がある。
f>buf-rdp を使えば、この後いくら他の数値変換を行っても my-f-string の内容は上書きされない。