なんか考えてることとか

変な人が主にプログラミング関連で考えていることをまとめる。

数字を含む文字列を安易に比較してはいけないという話

早速ですが、問題です。

"9"と"10"、比較するとどちらが大きいでしょうか?

ソートする際に文字列を比較することがある。その際、文字列に数字が含まれている場合、自分の思った通りに動かないことがあると言うことを今回書かせていただこうと思う。


さて、先ほど出した問題の答えだが、正解は"9"である。今回出した"9"と"10"は、文字列であり、少し意地悪をしてあえてそうであるとは言わなかったため、この問題は不適切であった。申し訳ない。

なぜ文字列の場合、"9"と"10"を比較すると"9"のほうが大きいことになるのか?それは文字列比較のアルゴリズムが密接にかかわってくる。

文字列の正体

文字列比較のアルゴリズムについて解説する前に、まず文字列の正体について知っている必要がある。なぜなら、Cなどの原始的なプログラミング言語を扱ったことがない人にとって、文字列とは単なる「値の一種」であるという認識でしかない可能性があるからだ。

実は文字列というのは、大元を辿れば「数値を格納する配列」なのである

たとえば、変数に"abc"という文字列を格納したとしよう。するとそれは4つの要素から成る配列であると考えることができる。

f:id:opaupafz2:20211030151609p:plain

つまり、1文字1文字の配列が文字列というわけである

さらに、文字は文字コードという「数値」によって表現されているため、以下の画像のように考えられ、文字列は大元は「数値を格納する配列」であることが言える。

f:id:opaupafz2:20211030152626p:plain

文字列比較のアルゴリズム

さて、文字列の正体がわかったところで文字列比較のアルゴリズムについてだが、至って簡単なことである。1文字1文字比較しているだけである
その際、1文字が等価でなかった場合その大小関係を返す。その後の文字がどうなっていようが関係ない。

たとえば、文字列"abc"と文字列"abbkfmntqlhps"を比較してみる。

f:id:opaupafz2:20211107141354g:plain

このように文字数関係なく(たとえ"abb..."がどれだけ"abc"よりも文字数が多かろうと)"abc"は"abbkfmntqlhps"よりも大きいと判断される。これが基本的な文字列比較アルゴリズムである。

ちなみに"abc"と"abcd"のような'a', 'b', 'c'までは同じだが、"abc"よりも"abcd"のほうが文字数が多い場合、以下のようになる。

f:id:opaupafz2:20211107143747g:plain

ある程度予想通りだが、4番目に出てきた'¥0'*1とは何か?これはNull文字と呼ばれるもので、文字コードは0である。文字列は末尾に必ずNull文字を付けると言う約束事がある。これがないと、文字列の終わりがわからないなどいろいろと問題が出てくるためである。
'¥0'の文字コードは0であるため、'd'(100)のほうが値としては大きい。故に"abc"は"abcd"よりも小さいと判断されるのである。

文字列比較アルゴリズムの欠点

以上が基本的な文字列比較アルゴリズムだが、これには大きな欠点がある。それはこれによって数の大小は比較できないということである。

文字列比較アルゴリズムの解説でも述べたが、最初の大小関係がわかれば、その後の文字がどうなっていようが関係ない
そのため、"9"と"10"を比較すると、まずは'9'と'1'が比較され、'9'(57)は'1'(49)よりも大きいため、文字列比較はここで終了し、"9"は"10"よりも大きいと判断される

f:id:opaupafz2:20211107152228g:plain

そのため、文字列比較を行うときは、数字はあくまで文字であり、数ではないために、数の大小は比較できないことに注意する必要があるわけである。

もし数の比較も含む文字列比較を行いたい場合、'0'~'9'の区間だけ特殊な方法で比較するか、整数型もしくは小数型に変換して比較するなどをする必要がある


いずれにしても、近年のプログラミング言語ですら文字列比較は数の比較をしないため、文字列比較を過信してはいけない。多くのプログラミング言語において、外部ライブラリを使うか、独自実装をする必要がある。

おまけ: 文字列比較アルゴリズムの欠点により引き起こされた問題

自分が文字列比較における問題に目を向けるようになるきっかけとなった問題を紹介しよう。
それが、2001年9月9日問題である。

ja.wikipedia.org

この問題は、2001年9月9日1時46分40秒(UTC)*2に時刻の値が999,999,999から1,000,000,000になる*3ことで不具合が発生するのではないか?とされていた問題である。

桁数が増えるだけで、特に問題は発生しないのではないか?と思われたが、数値を文字列として扱い、文字列比較をしたシステムにおいて、実際に2001年9月9日問題は起こってしまった

つまり、安易に文字列比較をすることは最悪致命的な不具合を引き起こしかねないことを2001年9月9日問題は証明したと言っても過言ではない。

以上から、自分は数字を含む文字列を安易に比較してはいけないということは周知されなければならない事実だと考えている。

*1:Linuxでは'¥'ではなく'\'である(そのため、Linuxでは'\0'となる)ため、注意

*2:日本標準時では2001年9月9日10時46分40秒

*3:コンピュータにおける時刻も実は数値であり、UNIX系では1970年1月1日0時0分0秒(UTC)を起点としてそこから何秒経ったかを値として表している