【JavaScript】全角を2文字・半角を1文字としてカウントする方法|表示幅の正しい数え方とバイト数・文字数との違い

日本語のフォームでは「全角は2文字、半角は1文字」として文字数を数えたい場面がよくあります。コメント欄の文字数制限や、固定幅での表示そろえ、Shift_JISのバイト数目安などで使われる数え方です。

ポイントは全角かどうかをコードポイントの範囲で正しく判定することです。よくある誤りとして「codePoint >= 0x10000 で判定」がありますが、これはサロゲートペア(絵文字・補助漢字)の判定であって全角判定ではありません(あ・漢字などの全角はBMP内なので検出できません)。正しい実装を見ていきましょう。

この記事の結論:for...of でコードポイント単位に走査し、半角(ASCII/Latin-1 と半角カナ)を1、それ以外(全角)を2として数えます。判定には /[\x00-\xFF\uFF61-\uFF9F]/ が便利です。
スポンサーリンク

正しいカウント関数

半角の範囲(ASCII〜Latin-1 の U+0000〜U+00FF と半角カナ U+FF61〜U+FF9F)を1、それ以外を全角として2を加算します。for...of はコードポイント単位で回るためサロゲートペアも安全に扱えます。

全角2・半角1でカウント
function getWidth(str) {
  let width = 0;
  for (const ch of str) { // for...of はコードポイント単位(サロゲートペア安全)
    // 半角 = ASCII/Latin-1(U+0000-U+00FF) と 半角カナ(U+FF61-U+FF9F)
    width += /[\x00-\xFF\uFF61-\uFF9F]/.test(ch) ? 1 : 2;
  }
  return width;
}

console.log(getWidth("あいうえおABCDE")); // 15(全角5×2 + 半角5×1)
console.log(getWidth("Hello"));          // 5
console.log(getWidth("ハンカク"));           // 4(半角カナは1)
旧来よく見る誤り:str.codePointAt(i) >= 0x10000 で全角を判定するコードは間違いです。0x10000 以上はサロゲートペア(絵文字・補助漢字)であり、あ・漢字・全角英数などの全角文字はBMP内(0x10000 未満)なので検出できません。さらに str.length(UTF-16単位)で回すとサロゲートペアを二重カウントします。

正規表現で全角の数から計算する

「各文字を最低1、全角はさらに+1」と考えると、全角の個数を数えるだけでも計算できます。

全角の個数から幅を計算
function countWidth(str) {
  // 全角 = 半角の範囲に当てはまらない文字
  const fullWidthCount = (str.match(/[^\x00-\xFF\uFF61-\uFF9F]/g) || []).length;
  // 体感文字数(コードポイント数)+ 全角の数だけ +1
  return [...str].length + fullWidthCount;
}

console.log(countWidth("あいうえおABCDE")); // 15

【重要】「全角2/半角1」はバイト数・文字数とは別物

「文字数を数える」と言っても、目的によって数え方が3通りあり、結果が異なります。混同に注意してください。

3つの数え方の違い
const s = "あA";

console.log([...s].length);                       // 2(体感文字数:全角も半角も1)
console.log(getWidth(s));                          // 3(全角2/半角1:表示幅)
console.log(new TextEncoder().encode(s).length);   // 4(UTF-8バイト数:全角3/半角1)
UTF-8のバイト数(全角=3バイト)を数えたい場合は文字列のバイト数を取得する方法を参照してください。「全角2/半角1」は主に表示幅やShift_JISバイトの目安、フォームの全角換算制限に使う数え方です。

絵文字・サロゲートペアの扱い

for...of はサロゲートペア(絵文字 🎉 など)を1つのコードポイントとして扱うため、上の getWidth では絵文字も全角と同じく2としてカウントされます。(絵文字を1幅・2幅どちらで数えるかは要件次第なので、必要なら個別に分岐してください。)

絵文字の幅
console.log(getWidth("\u{1F389}A")); // 3(絵文字=2 + 半角A=1)

// もし旧コードのように str.length で回すと…
console.log("\u{1F389}".length);     // 2(サロゲートペアで2単位)→ 二重カウントの原因

実用例:全角2字換算の入力カウンタ

「全角200文字(=半角400相当)まで」のような制限を、リアルタイムに表示できます。

textarea の全角換算カウンタ
const input = document.getElementById("comment");
const counter = document.getElementById("counter");
const MAX = 400; // 半角換算の上限(全角なら200文字)

input.addEventListener("input", () => {
  const w = getWidth(input.value);
  counter.textContent = `${w} / ${MAX}`;
  counter.style.color = w > MAX ? "red" : "#333";
});

数え方の比較

数え方 "あ" "A" 主な用途
体感文字数 [...str].length 1 1 見た目の文字数
全角2/半角1(本記事) 2 1 表示幅・Shift_JIS目安・フォーム制限
UTF-8バイト数 3 1 通信量・DB容量(詳細

よくある質問(FAQ)

Q全角を2、半角を1として数えるにはどうすればいいですか?
Afor...of で1文字ずつ走査し、半角(/[\x00-\xFF\uFF61-\uFF9F]/ に一致)なら+1、それ以外(全角)なら+2します。記事内の getWidth() がその実装です。
QcodePoint >= 0x10000 で全角を判定してはいけないのはなぜ?
A0x10000 以上はサロゲートペア(絵文字・補助漢字)の範囲で、全角判定ではないからです。あ・漢字・全角英数などの全角文字はBMP内(0x10000 未満)にあるため、この条件では検出できず全角が1としてカウントされてしまいます。
Q「全角2/半角1」とバイト数は同じですか?
A違います。UTF-8では全角は3バイトなので「全角2/半角1」とは一致しません。「全角2/半角1」は表示幅やShift_JISバイトの目安、フォームの全角換算制限に使う数え方です。UTF-8バイト数が必要なら new TextEncoder().encode(str).length を使います。
Q半角カナ(ハンカク)は1としてカウントされますか?
Aはい。半角カナ(U+FF61〜U+FF9F)は半角なので1として数えます。記事の判定式はこの範囲を含めているため、getWidth("ハンカク") は4を返します。

まとめ

JavaScriptで全角を2・半角を1として数える方法のポイントを整理します。

  • for...of でコードポイント単位に走査し、半角=1・全角=2で加算
  • 半角判定は /[\x00-\xFF\uFF61-\uFF9F]/(ASCII/Latin-1+半角カナ)
  • codePoint >= 0x10000 での全角判定は誤り(それはサロゲートペア判定)
  • 「全角2/半角1」は表示幅の数え方で、UTF-8バイト数(全角3)・体感文字数(全角1)とは別物
  • 絵文字は for...of なら全角と同じく2でカウントされる

関連として、文字列のバイト数を取得する方法全角英数を半角に変換する方法文字を1文字ずつ取得する方法もあわせて確認すると、文字幅の扱いに強くなれます。