# 機能設計書 30-浮動小数点フォーマッタ

## 概要

本ドキュメントは、mrubyランタイムにおける浮動小数点フォーマッタ機能の設計を記述する。mrb_float値を文字列表現に変換する低レベルフォーマット関数を提供する。

### 本機能の処理概要

浮動小数点フォーマッタは、mrb_float（doubleまたはfloat）値をprintf互換のフォーマット指定（e/f/g形式）に従って文字列に変換する。Fred Bayerのpdouble.cをベースに、Matz（松本行弘氏）がdoubleサポートおよび代替形式（#修飾子）を追加した実装である。

**業務上の目的・背景**：組み込み環境ではCライブラリのsnprintf/sprintfが利用できない、または実装品質が不十分な場合がある。mruby独自の浮動小数点フォーマッタにより、プラットフォーム非依存で一貫した出力を実現する。

**機能の利用シーン**：Float#to_sによる文字列変換、Kernel#sprintfでの%e/%f/%gフォーマット、Float#inspectの出力。

**主要な処理内容**：
1. 符号処理（負数、正符号指定、スペース指定）
2. 特殊値処理（Infinity、NaN）
3. 指数の算出（2進べき乗分割法）
4. フォーマット形式別の桁数計算（e/f/g形式）
5. 仮数部の数字出力
6. 丸め処理（5以上の切り上げ、桁繰り上がり対応）
7. 指数部の出力
8. g形式での末尾ゼロ除去

**関連システム・外部連携**：数値処理（numeric.c）、sprintf（mruby-sprintf）。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | C API関数として内部的に利用される |

## 機能種別

ユーティリティ（数値→文字列変換）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| f | mrb_float | Yes | フォーマット対象の浮動小数点値 | - |
| buf | char* | Yes | 出力バッファ | - |
| buf_size | size_t | Yes | バッファサイズ | FLT_MIN_BUF_SIZE以上推奨 |
| fmt | char | Yes | フォーマット指定（'e','E','f','F','g','G'、ビット7で代替形式） | - |
| prec | int | Yes | 精度（-1でデフォルト6） | 負値はデフォルト適用 |
| sign | char | Yes | 正数の符号文字（'\0':なし、'+':プラス、' ':スペース） | - |

### 入力データソース

- numeric.cからのFloat#to_s変換
- mruby-sprintfからの%e/%f/%gフォーマット

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | int | 書き込まれた文字数（終端'\0'を含まない） |
| buf | char[] | フォーマットされた文字列（終端'\0'付き） |

### 出力先

呼び出し元が提供するバッファへの書き込み

## 処理フロー

### 処理シーケンス

```
1. mrb_format_float(f, buf, buf_size, fmt, prec, sign)
   ├─ 代替形式フラグ検出（fmt & 0x80）
   ├─ バッファサイズチェック（FLT_MIN_BUF_SIZE未満なら'?'出力）
   ├─ 符号処理
   │   ├─ 負数: '-'出力、f = -f
   │   └─ 正数+sign指定: sign文字出力
   ├─ 特殊値処理
   │   ├─ Infinity: "INF"/"inf"出力→即return
   │   └─ NaN: "NAN"/"nan"出力→即return
   ├─ 精度デフォルト設定（prec<0 → prec=6）
   ├─ 指数算出（2進べき乗分割法）
   │   ├─ f == 0.0: e=0
   │   ├─ f < 1.0: 負の指数算出（g_neg_pow/g_pos_pow使用）
   │   └─ f >= 1.0: 正の指数算出（g_pos_pow/g_neg_pow使用）
   ├─ フォーマット形式決定
   │   ├─ g形式: eの値によりf/e形式に分岐
   │   ├─ f形式: バッファ溢れ時はe形式にフォールバック
   │   └─ e形式: 精度=有効桁数-1
   ├─ 仮数部の数字出力（ループ）
   ├─ 丸め処理（f >= 5.0で繰り上がり）
   │   └─ 9→0の繰り上がり連鎖、桁溢れ時は1を挿入
   ├─ g形式: 末尾ゼロ除去（alt_form以外）
   └─ 指数部出力（e_sign設定時）
```

### フローチャート

```mermaid
flowchart TD
    A[mrb_format_float] --> B{buf_size < MIN?}
    B -->|Yes| C["'?' 出力・return"]
    B -->|No| D[符号処理]
    D --> E{特殊値?}
    E -->|Inf| F["INF/inf 出力"]
    E -->|NaN| G["NAN/nan 出力"]
    E -->|通常値| H[精度デフォルト設定]
    H --> I{f の範囲}
    I -->|f == 0| J[e=0]
    I -->|f < 1.0| K[負の指数算出]
    I -->|f >= 1.0| L[正の指数算出]
    J --> M{fmt形式}
    K --> M
    L --> M
    M -->|e形式| N[num_digits = prec+1]
    M -->|f形式| O[num_digits = prec+e+1]
    M -->|g形式| P[num_digits = prec]
    N --> Q[仮数部出力ループ]
    O --> Q
    P --> Q
    Q --> R{丸め f>=5.0?}
    R -->|Yes| S[繰り上がり処理]
    R -->|No| T{g形式?}
    S --> T
    T -->|Yes| U[末尾ゼロ除去]
    T -->|No| V{指数出力?}
    U --> V
    V -->|Yes| W[e_char + e_sign + 数字]
    V -->|No| X[終端'\0'・return]
    W --> X
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 代替形式 | fmt上位ビット（0x80）で代替形式（#修飾子）を指定 | fmt & 0x80 |
| BR-02 | 大文字/小文字 | fmt & 0x20でE/e、G/gの大小文字を判定 | - |
| BR-03 | g形式精度 | g形式でprec==0の場合、prec=1に補正 | fmt=='g', prec==0 |
| BR-04 | g→f変換 | g形式で指数 <= 4（f<1.0）またはe < prec（f>=1.0）の場合、f形式に切替 | fmt=='g' |
| BR-05 | f→e変換 | f形式でe >= buf_remainingの場合、e形式にフォールバック | fmt=='f' |
| BR-06 | 末尾ゼロ除去 | g形式（代替形式以外）で末尾の'0'と'.'を除去 | org_fmt=='g', !alt_form |
| BR-07 | 丸め規則 | 最後の桁の次が5.0以上なら切り上げ | 全形式 |
| BR-08 | FLT_MIN_BUF_SIZE | float32: 6バイト（-9e+99）、double: 7バイト（-9e+199） | バッファサイズチェック |
| BR-09 | MRB_NO_FLOAT | MRB_NO_FLOAT定義時は関数全体が無効 | コンパイル時 |
| BR-10 | MRB_USE_FLOAT32 | 定義時はfloat32用の定数を使用（FLT_DECEXP=32等） | コンパイル時 |

### 計算ロジック

| 計算名 | 計算式 | 説明 |
|--------|--------|------|
| 指数算出（正） | for e1=FLT_DECEXP; e1; e1>>=1 | 2進べき乗分割法で指数を求める |
| 仮数部数字 | d = (int8_t)((int)f) % 10 | 最上位桁を取得 |
| 仮数部シフト | f -= d; f *= 10.0 | 次の桁へ移動 |
| 精度バッファ制限 | prec = buf_remaining - FLT_MIN_BUF_SIZE | バッファオーバーフロー防止 |

## データベース操作仕様

本機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バッファ不足 | buf_size <= FLT_MIN_BUF_SIZE | '?'を出力して早期return |
| - | Infinity | isinf(f) | "INF"/"inf"を出力 |
| - | NaN | isnan(f) | "NAN"/"nan"を出力 |

### リトライ仕様

リトライは不要。

## トランザクション仕様

特になし。

## パフォーマンス要件

- g_pos_pow/g_neg_powテーブルは6エントリ（double）または3エントリ（float32）の小さいテーブルで高速参照
- 2進べき乗分割法（binary splitting）により、最大6回（double）のループで指数を確定
- 丸め処理は文字列バッファを後方走査する線形処理
- 動的メモリ確保なし（呼び出し元提供のバッファのみ使用）

## セキュリティ考慮事項

- バッファサイズを厳密にチェックし、バッファオーバーフローを防止
- 精度がバッファ残量を超える場合は自動的にクランプ
- f形式でバッファに収まらない場合はe形式にフォールバック

## 備考

- Fred Bayerのpdouble.c（Public Domain）をベースにMatz（松本行弘氏）がdoubleサポートと代替形式を追加
- MRB_USE_FLOAT32定義時はfloat32用の定数セット（FLT_DECEXP=32, FLT_ROUND_TO_ONE=0.9999995F, FLT_MIN_BUF_SIZE=6）が使用される
- double（デフォルト）の場合はFLT_DECEXP=256, FLT_ROUND_TO_ONE=0.999999999995, FLT_MIN_BUF_SIZE=7が使用される
- g_pos_pow/g_neg_powテーブルはdouble時に{1e256,1e128,1e64,1e32,1e16,1e8,1e4,1e2,1e1}の9エントリ、float32時は{1e32,1e16,1e8,1e4,1e2,1e1}の6エントリ

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

2進べき乗テーブルがフォーマッタの指数算出の鍵である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | fmt_fp.c | `src/fmt_fp.c` | MRB_USE_FLOAT32条件分岐（34-53行目）: float32/double用の定数定義 |
| 1-2 | fmt_fp.c | `src/fmt_fp.c` | g_pos_pow配列（55-60行目）: 正のべき乗テーブル |
| 1-3 | fmt_fp.c | `src/fmt_fp.c` | g_neg_pow配列（61-66行目）: 負のべき乗テーブル |
| 1-4 | numeric.h | `include/mruby/numeric.h` | mrb_format_float宣言（168行目） |

**定数定義**（double/float32）:
- `FLT_DECEXP`: 256（double）/ 32（float32）- 2進べき乗分割法の初期値
- `FLT_ROUND_TO_ONE`: 0.999999999995（double）/ 0.9999995F（float32）- 1.0への丸め閾値
- `FLT_MIN_BUF_SIZE`: 7（double）/ 6（float32）- 最小バッファサイズ

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | fmt_fp.c | `src/fmt_fp.c` | mrb_format_float（78-371行目）: 関数全体 |

**主要処理フロー**:

1. **84-87行目**: 代替形式フラグ（fmt & 0x80）の抽出
2. **88-99行目**: バッファサイズチェック、FLT_MIN_BUF_SIZE以下なら'?'出力
3. **100-106行目**: 符号処理（signbit(f)で負数判定、sign引数で正数符号）
4. **109-125行目**: 特殊値処理（isinf→"INF"、isnan→"NAN"、大文字/小文字はfmt & 0x20で制御）
5. **127-135行目**: 精度デフォルト設定、g形式でprec==0なら1に補正
6. **143-151行目**: f==0.0の場合の処理
7. **152-216行目**: f<1.0の場合の負の指数算出（159-164行目のfor文が核心）
8. **217-260行目**: f>=1.0の場合の正の指数算出（219-224行目のfor文が核心）
9. **277-283行目**: フォーマット形式別のnum_digits計算
10. **286-294行目**: 仮数部の数字出力ループ
11. **297-346行目**: 丸め処理（5.0以上で切り上げ、繰り上がり連鎖）
12. **348-356行目**: g形式での末尾ゼロ除去
13. **358-367行目**: 指数部の出力

#### Step 3: 指数算出の2進べき乗分割法を理解する

指数算出は本関数の核心的アルゴリズムである。

**負の指数算出（f < 1.0の場合、159-164行目）**:
```c
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
    if (*neg_pow > f) {
        e += e1;
        f *= *pos_pow;
    }
}
```
- e1はFLT_DECEXP（256）から1まで2の冪で降下
- fが各負のべき乗より小さい場合、対応する正のべき乗を掛けて正規化
- 最終的にfは1.0〜10.0の範囲に収まる

**正の指数算出（f >= 1.0の場合、219-224行目）**:
```c
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
    if (*pos_pow <= f) {
        e += e1;
        f *= *neg_pow;
    }
}
```
- 同様の2分法だが、正のべき乗と比較して負のべき乗を掛ける

#### Step 4: 丸め処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | fmt_fp.c | `src/fmt_fp.c` | 丸め処理（297-346行目）: バッファ末尾から逆走査 |

**丸め処理の流れ**:
1. **297行目**: f >= 5.0なら丸め実行
2. **300-319行目**: バッファを後方走査し、'.'はスキップ、'9'未満ならインクリメントして終了、'9'なら'0'にして繰り上がり
3. **320-345行目**: 先頭まで繰り上がった場合、'1'を挿入し、e/f形式に応じて小数点を移動

### プログラム呼び出し階層図

```
mrb_format_float (fmt_fp.c:78)
    │
    ├─ signbit() - 負数判定
    ├─ isinf() - 無限大判定
    ├─ isnan() - NaN判定
    ├─ g_pos_pow[] / g_neg_pow[] テーブル参照
    └─ 内部処理のみ（外部関数呼び出しなし）

--- 呼び出し元 ---
numeric.c (Float#to_s)
    └─ mrb_format_float(mrb_float(flo), buf, sizeof(buf), 'g', prec, '\0')

sprintf.c (Kernel#sprintf %e/%f/%g)
    └─ mrb_format_float(f, buf, buf_size, fmt, prec, sign)
```

### データフロー図

```
[入力]                     [処理]                              [出力]

mrb_float f       ───▶ 符号処理                        ───▶ '-' or sign
                         │
                   ───▶ 特殊値判定                      ───▶ "INF"/"NAN"
                         │
                   ───▶ 指数算出（2進べき乗分割法）
                         │  f を 1.0〜10.0 に正規化
                         │  e に10進指数を格納
                         │
                   ───▶ フォーマット形式決定（e/f/g）
                         │
                   ───▶ 仮数部数字出力                  ───▶ buf[] に数字列
                         │
                   ───▶ 丸め処理                        ───▶ 繰り上がり反映
                         │
                   ───▶ 末尾処理                        ───▶ 指数部/ゼロ除去
                         │
char *buf          ◀── 終端'\0'                         ───▶ int 文字数
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| fmt_fp.c | `src/fmt_fp.c` | ソース | mrb_format_float: 浮動小数点フォーマッタ本体 |
| numeric.h | `include/mruby/numeric.h` | ヘッダ | mrb_format_float関数宣言（168行目） |
| numeric.c | `src/numeric.c` | ソース | Float#to_sからの呼び出し元（428行目） |
| sprintf.c | `mrbgems/mruby-sprintf/src/sprintf.c` | ソース | sprintf %e/%f/%gからの呼び出し元（190行目） |
