Newer
Older
client_dwatch / lib / format / number.ts
/**
 * 数値の表示整形(指示書AN 段階5 / §8-4)。
 *
 * ★★なぜ新設したか★★ — ★`341635` は読めない★。3桁区切りが要るが、
 *   本番コードに `toLocaleString` / `Intl.NumberFormat` の使用は1件も無かった(実測)。
 *   ★各画面で `toLocaleString()` を直接呼ばないこと★ — 下の「ロケール」の問題を
 *   画面の数だけ抱え込むことになる。
 *
 * ------------------------------------------------------------------------
 * ★★ロケールを固定する理由★★
 * ------------------------------------------------------------------------
 * `value.toLocaleString()`(引数なし)は ★実行環境のロケール★ を使う。
 * サーバ(Node)とブラウザでロケールが違えば、★同じ数値が違う文字列になる★
 * (区切りが `.` になる地域もある)。SSR とクライアントで文字列が食い違えば
 * ハイドレーション不整合にもなる。★だからロケールを明示して固定する★。
 * `ja-JP` と `en-US` は3桁区切り・`,` で同じ結果なので、
 * ★JP / EN 切替(`lib/i18n.ts`)で表示が変わることはない★。
 *
 * ------------------------------------------------------------------------
 * ★★丸めは表示層だけ★★(`CLAUDE.md` §4.3 / §9.3)
 * ------------------------------------------------------------------------
 * 面積は `double` なので合計に小数が出うる。★整数にするのはここ(表示)だけ★で、
 * ★判定・集計には生値を渡す★(`lib/clean-area/aggregate.ts` は丸めない)。
 */
import { AREA_UNIT } from "@/lib/config";

/**
 * 有限でない値のときに出す文字列。
 *
 * ★`0` と書かない★ — 「0だった」と「数えられなかった」は別の事実で、
 *   `displayRate()`(`lib/judge/grade.ts`)が `null` に `-` を出すのと揃える。
 */
const NOT_AVAILABLE = "-";

/**
 * ★区切りを固定するための明示ロケール★(上の注記)。
 * 3桁区切り・`,` は `ja-JP` でも同じ。
 */
const GROUPING_LOCALE = "en-US";

/**
 * 整数に丸めて3桁区切りにする(例: `341635.4` → `"341,635"`)。
 *
 * @param value 生値。★`NaN` / `Infinity` は `"-"`★(★`∞` を画面に出さない★)
 */
export function formatNumber(value: number): string {
  if (!Number.isFinite(value)) return NOT_AVAILABLE;
  // `Math.round` を先に通す(`maximumFractionDigits: 0` 任せにしない)—
  // 端数処理の規則(小数第1位を四捨五入。`CLAUDE.md` §4.3)を
  // Intl の実装依存にしないため。★-0 を `"-0"` と出さないように 0 を足す★
  return new Intl.NumberFormat(GROUPING_LOCALE).format(Math.round(value) + 0);
}

/**
 * 面積を「3桁区切り+単位」で出す(例: `341635` → `"341,635㎡"`)。
 *
 * ★★単位は `AREA_UNIT`(`lib/config`)から出す★★(指示書AN §2-4 / 落とし穴6)—
 *   ★`"㎡"` を画面や API に直書きしないこと★。TeamWorks の確認が未取得なので、
 *   回答が違ったときに直す場所を1箇所に保つ。
 */
export function formatArea(value: number): string {
  return `${formatNumber(value)}${AREA_UNIT}`;
}