この質問は馬鹿げているように聞こえるかもしれませんが、なぜ_0
_はfalse
に評価され、true
に対する他の[整数]値はほとんどのプログラミング言語ですか?
質問は少し単純すぎるようですので、もう少し説明します。最初に、それはプログラマーには明らかであるように見えるかもしれませんが、なぜプログラミング言語がないのでしょうか。私は使用しました-_0
_はtrue
に、他のすべての[整数]値はfalse
に評価されますか?その1つの発言はランダムに見えるかもしれませんが、私はそれが良い考えであったかもしれないいくつかの例を持っています。まず、文字列の3者間比較の例を見てみましょう。Cのstrcmp
を例にとります。最初の言語としてCを試すプログラマは、次のコードを書くように思われるかもしれません。
_if (strcmp(str1, str2)) { // Do something... }
_
strcmp
は、文字列が等しい場合にfalse
と評価される_0
_を返すため、初心者のプログラマが何をしようとしていたのかがわからず失敗し、通常は最初は理由がわかりません。代わりに_0
_がtrue
に評価された場合、この関数は、最も単純な式(上記の式)で同等性を比較するときに使用でき、_-1
_および_1
_の適切なチェックで必要なときにのみ行われます。ほとんどの場合、私たちは戻り値の型をbool
(私が考えている限りでは)と見なしていました。
さらに、_-1
_、_0
_、および_1
_の値のみを受け取る新しい型sign
を導入しましょう。それはかなり便利です。 C++に spaceship operator があり、_std::string
_に必要だと想像してください(まあ、compare
関数はすでにありますが、宇宙船演算子の方が楽しいです)。現在、宣言は次のようになります。
_sign operator<=>(const std::string& lhs, const std::string& rhs);
_
_0
_がtrue
に評価されていた場合、宇宙船演算子は存在しなかったため、_operator==
_を次のように宣言することができます。
_sign operator==(const std::string& lhs, const std::string& rhs);
_
この_operator==
_は、3者間比較を一度に処理し、次のチェックを実行するために引き続き使用でき、必要に応じて、どの文字列が他の文字列よりも辞書的に優れているかを確認できます。
_if (str1 == str2) { // Do something... }
_
現在は例外があるため、この部分は、そのようなものが存在しない古い言語(Cなど)にのみ適用されます。 Cの標準ライブラリー(およびPOSIXのライブラリーも)を見ると、maaaaany関数が成功すると_0
_を返し、そうでなければ整数を返すことが確実にわかります。悲しいことに、一部の人がこの種のことをしています。
_#define TRUE 0
// ...
if (some_function() == TRUE)
{
// Here, TRUE would mean success...
// Do something
}
_
プログラミングにおける考え方を考えると、多くの場合、次のような推論パターンがあります。
_Do something
Did it work?
Yes ->
That's ok, one case to handle
No ->
Why? Many cases to handle
_
もう一度考えてみると、_0
_だけをyes
に設定するのが理にかなっています(それがCの関数のしくみです)。他のすべての値は、 no
。ただし、私が知っているすべてのプログラミング言語では(おそらくいくつかの実験的エセコリック言語を除いて)、yes
はfalse
条件でif
と評価され、すべてのno
ケースはtrue
と評価されます。 「動作する」が1つのケースを表し、「動作しない」が多くの考えられる原因を表す多くの状況があります。そのように考えると、_0
_をtrue
に評価し、残りをfalse
に評価させることは、もっと理にかなっているでしょう。
私の結論は本質的に私の元の質問です:上記のいくつかの例を考慮して、_0
_がfalse
で他の値がtrue
である言語を設計したのはなぜですか?
フォローアップ:多くのアイデアを持つ多くの回答があり、それがそのようになる理由として考えられる多くの理由があるのは素晴らしいことです。私はあなたがそれについて情熱的であるように見えるのが大好きです。私はもともとこの質問を退屈なところから出していましたが、あなたはとても情熱的であるように思われるので、少し先に進んで質問します と1のブール値の選択の背後にある根拠 Math.SE :)
0
はfalse
です。これは、どちらも共通のゼロ要素であるためです semirings 。それらは異なるデータ型ですが、同型代数構造に属しているため、それらを変換することは直感的に理解できます。
0
は、加算の識別と乗算のゼロです。これは整数と有理数に当てはまりますが、IEEE-754浮動小数点数ではありません:0.0 * NaN = NaN
および0.0 * Infinity = NaN
。
false
は、ブールxor(⊻)のIDであり、ブールand(∧)。ブール値が{0、1}(2を法とする整数のセット)として表される場合、⊻はキャリーなしの加算であり、∧は乗算であると考えることができます。
""
および[]
は連結のIDですが、ゼロとして意味のある操作がいくつかあります。繰り返しは1つですが、繰り返しと連結は分散されないため、これらの演算はセミリングを形成しません。
このような暗黙の変換は小さなプログラムでは役立ちますが、大きなプログラムでは、プログラムの推論がより困難になる可能性があります。言語設計における多くのトレードオフの1つにすぎません。
数学がうまくいくから。
FALSE OR TRUE is TRUE, because 0 | 1 is 1.
... insert many other examples here.
従来、Cプログラムには次のような条件があります。
if (someFunctionReturningANumber())
のではなく
if (someFunctionReturningANumber() != 0)
ゼロがfalseに等しいという概念はよく理解されているからです。
他の人が言ったように、数学が最初に来ました。これが、0がfalse
で、1がtrue
である理由です。
どの数学について話しているのですか? ブール代数 デジタルコンピュータが登場するずっと前の1800年代半ばからの日付。
規約は 命題論理 から来たとも言え、ブール代数よりも古い。これは、プログラマーが知っており、愛している多くの論理的な結果を形式化したものです(false || x
はx
と等しい、true && x
はx
と等しくなります)。
基本的に、2つの要素を持つセットの算術について話します。バイナリでカウントすることを考えてください。ブール代数はこの概念の起源であり、その理論的基盤です。 Cのような言語の規則は、単純なアプリケーションにすぎません。
この記事 で説明されているように、値false
およびtrue
は整数0および1と混同しないでください。ただし、ガロア体の要素で識別できます。 (有限体)2つの要素( ここ を参照)。
フィールドは、特定の公理を満たす2つの演算のセットです。
記号0および1は、フィールドの加法的および乗法的IDを表すために慣習的に使用されています。これは、実数は、IDが0および1であるフィールド(有限ではない)でもあるためです。
加法的アイデンティティはフィールドの要素0であり、すべてのxに対して次のようになります。
x + 0 = 0 + x = x
そして乗法的同一性は、すべてのxに対して次のようなフィールドの要素1です。
x * 1 = 1 * x = x
2つの要素の有限体には、これらの2つの要素、つまり、加法恒等式0(またはfalse
)と乗法的恒等1(またはtrue
)のみがあります。このフィールドの2つの演算は、論理XOR(+)と論理AND(*)です。
注演算を反転すると(XORは乗算であり、ANDは加算です)、乗算は加算よりも分散的ではなく、もうフィールド。このような場合、2つの要素0と1を(任意の順序で)呼び出す理由はありません。 XORの代わりにORの演算を選択できないことにも注意してください。OR/ANDを加算/乗算としてどのように解釈しても、結果の構造はフィールド(すべての逆要素がフィールド公理の要求どおりに存在するわけではありません)。
C関数について:
strcmp
は2つの文字列の差を計算します。 0は、2つの文字列の間に差がないこと、つまり2つの文字列が等しいことを意味します。上記の直感的な説明は、戻り値の解釈を思い出すのに役立ちますが、ライブラリのドキュメントを確認するだけでも簡単です。
代替システムも許容可能な設計決定である可能性があることを考慮する必要があります。
0の終了ステータスをtrueとして扱うシェルの例については、すでに説明しました。
$ ( exit 0 ) && echo "0 is true" || echo "0 is false"
0 is true
$ ( exit 1 ) && echo "1 is true" || echo "1 is false"
1 is false
根拠には、成功する方法は1つありますが、失敗する方法は多数あるため、「エラーなし」を意味する特別な値として0を使用するのが実用的です。
「通常の」プログラミング言語の中には、Rubyのように、0を真の値として扱ういくつかの異常値があります。
$ irb
irb(main):001:0> 0 ? '0 is true' : '0 is false'
=> "0 is true"
根拠 は、false
とnil
のみがfalseであることです。多くのRuby初心者にとって、それはおかしなことですが、場合によっては、0が他の数値と同じように扱われるのはいいことです。
irb(main):002:0> (pos = 'axe' =~ /x/) ? "Found x at position #{pos}" : "x not found"
=> "Found x at position 1"
irb(main):003:0> (pos = 'xyz' =~ /x/) ? "Found x at position #{pos}" : "x not found"
=> "Found x at position 0"
irb(main):004:0> (pos = 'abc' =~ /x/) ? "Found x at position #{pos}" : "x not found"
=> "x not found"
ただし、このようなシステムは、ブール値を数値とは別の型として区別できる言語でのみ機能します。コンピューティングの初期の頃は、アセンブリ言語や生の機械語で作業するプログラマには、そのような贅沢はありませんでした。 0を「空白」の状態として扱い、コードが何かが発生したことを検出したときにビットをフラグとして1に設定するのはおそらく自然なことです。拡張により、この規約は、ゼロは偽として扱われ、ゼロ以外の値は真として扱われるようになった。ただし、そのようにする必要はありません。
Javaでは、true
とfalse
が唯一のブール値です。数値はブール値ではなく、ブール値にキャストすることもできません( Java言語仕様、セクション4.2.2 ):
整数型と
boolean
型の間のキャストはありません。
このルールは、質問を完全に回避するだけです。すべてのブール式は、コードで明示的に記述する必要があります。
一般的なケースに取り組む前に、カウンターの例について説明します。
文字列比較
同じことが、実際には多くの種類の比較にも当てはまります。このような比較では、2つのオブジェクト間の距離が計算されます。オブジェクトが等しい場合、距離は最小になります。したがって、「比較が成功した」場合、値は0です。しかし、実際には、strcmp
の戻り値はnotブール値であり、距離であり、プログラマが知らないことをトラップするものです。 if (strcmp(...)) do_when_equal() else do_when_not_equal()
。
C++では、strcmp
を再設計してDistance
オブジェクトを返し、operator bool()
をオーバーライドして0のときにtrueを返すことができます(ただし、別の一連の問題に悩まされます)。 。または、プレーンCでは、文字列が等しい場合は1を返し、それ以外の場合は0を返すstreq
関数を使用します。
API呼び出し/プログラム終了コード
ここでエラーが発生した場合に決定が下がるので、問題が発生した理由を気にします。物事が成功すると、あなたは特に何も知りたくありません-あなたの意図は実現されます。したがって、戻り値はこの情報を伝える必要があります。 notブール値であり、エラーコードです。特別なエラー値0は、「エラーなし」を意味します。範囲の残りの部分は、処理する必要のあるローカルで意味のあるエラーを表します(1を含め、これは多くの場合「不特定のエラー」を意味します)。
一般的なケース
これは私たちに疑問を残します:ブール値True
とFalse
が通常それぞれ1と0で表されるのはなぜですか?
まあ、主観的な「この方法で気持ちが良くなる」という議論の他に、ここに私が考えることができるいくつかの理由(主観的)があります。
電気回路のアナロジー。電流は1秒間オンで、0秒間オフです。別のミックスよりも(1、Yes、True、On)と(0、No、False、Off)を一緒に使うのが好きです
メモリの初期化。 memset(0)
変数(整数、浮動小数点数、ブール値)を束ねるとき、その値を最も保守的な仮定に一致させます。例えば。私の合計は最初は0、述語はFalseなどです。
これらの理由はすべて私の教育に関係しているのかもしれません-もし私が最初から0をTrueに関連付けるように教えられていたなら、私は逆の方向に進みます。
高レベルの観点から、あなたは3つの全く異なるデータ型について話している:
ブール。 ブール代数 の数学的規則は、false
に0を使用し、true
に1を使用することなので、その規則に従うことは理にかなっています。この方法も直感的に理解できると思います。
比較の結果。これには、_<
_、_=
_、_>
_の3つの値があります(true
がないことに注意してください)。それらの場合、それぞれ-1、0、1の値(または、より一般的には、負の値、0、正の値)を使用することは理にかなっています。
等価性をチェックしたいが、一般的な比較を実行する関数しかない場合は、strcmp(str1, str2) == 0
のようなものを使用して、それを明示的にする必要があると思います。この状況で_!
_を使用すると混乱します。ブール値ではない値をブール値として扱うためです。
また、比較と平等は同じものでなくてもかまいません。たとえば、人々を生年月日で並べ替えると、Compare(me, myTwin)
は_0
_を返しますが、Equals(me, myTwin)
はfalse
を返します。
関数の成功または失敗。場合によっては、その成功または失敗の詳細も含まれます。 Windowsについて話している場合、このタイプは HRESULT
と呼ばれ、ゼロ以外の値は必ずしも失敗を示すわけではありません。実際、negative値は失敗と非負の成功を示します。成功値はたいてい_S_OK = 0
_ですが、たとえば_S_FALSE = 1
_や他の値にすることもできます。
混乱は、3つの論理的にまったく異なるデータ型が実際にはCおよび他のいくつかの言語で単一のデータ型(整数)として表され、条件で整数を使用できるという事実から生じます。しかし、ブール値を再定義して、いくつかの非ブール型を条件でより簡単に使用できるようにするのは意味がないと思います。
また、Cの条件でよく使用される別の型、ポインターについても検討してください。そこでは、NULL
ポインター(_0
_として表される)をfalse
として扱うのが自然です。したがって、あなたの提案に従うと、ポインタの操作も難しくなります。 (ただし、個人的には、ポインターをブール値として扱うのではなく、明示的にNULL
と比較することをお勧めします。)
ほとんどのCPUには分岐に使用できるZEROフラグがあるため、ゼロはfalseになる可能性があります。比較操作を保存します。
その理由を見てみましょう。
聴衆はおそらく議会を読んでいないので、いくつかの擬似コード
c-単純なループ呼び出しを10回振る
for (int foo =10; foo>0; foo-- ) /* down count loop is shorter */
{
wibble();
}
いくつかはそのためのアセンブリを装います
0x1000 ld a 0x0a 'foo=10
0x1002 call 0x1234 'call wibble()
0x1005 dec a 'foo--
0x1006 jrnz -0x06 'jump back to 0x1000 if not zero
0x1008
c-別の単純なループ呼び出しを10回wibble呼び出します
for (int foo =0; foo<10; foo-- ) /* up count loop is longer */
{
wibble();
}
このケースのためのいくつかのふりアセンブリ
0x1000 ld a 0x00 'foo=0
0x1002 call 0x1234 'call wibble()
0x1005 dec a 'foo--
0x1006 cmp 0x0a 'compare foo to 10 ( like a subtract but we throw the result away)
0x1008 jrns -0x08 'jump back to 0x1000 if compare was negative
0x100a
もう少しcソース
int foo=10;
if ( foo ) wibble()
と議会
0x1000 ld a 0x10
0x1002 jz 0x3
0x1004 call 0x1234
0x1007
それがどれだけ短いか見てください
もう少しcソース
int foo=10;
if ( foo==0 ) wibble()
およびアセンブリ(== 0を比較なしで置き換えることができるわずかにスマートなコンパイラを想定してみましょう)
0x1000 ld a 0x10
0x1002 jz 0x3
0x1004 call 0x1234
0x1007
ここで、true = 1の規則を試してみましょう
さらにcソース#define TRUE 1 int foo = TRUE; if(foo == TRUE)wibble()
と議会
0x1000 ld a 0x1
0x1002 cmp a 0x01
0x1004 jz 0x3
0x1006 call 0x1234
0x1009
ゼロ以外のtrueの場合がどれだけ短いかを参照してください。
本当に初期のCPUには、アキュムレータに接続されたフラグの小さなセットがありました。
A> bまたはa = bであるかどうかを確認するには、通常、比較命令を使用します。
これを言い換えましょう。一部の古いCPUでは、アキュムレータがゼロに等しいか、アキュムレータがゼロより小さい場合に比較命令を使用する必要がありませんでした。
ここで、なぜゼロが偽であるのかわかりますか?
これは疑似コードであり、実際の命令セットはこのようなものではないことに注意してください。アセンブリを知っている場合は、ここで多くのことを簡略化していることを知っています。コンパイラの設計について何か知っている場合は、その必要はありませんでした。この答えを読んでください。ループの展開や分岐予測について何か知っている人なら、上級クラスは203号室のホールにいます。
奇妙なことに、ゼロは常に偽であるとは限りません。
特に、UnixおよびPosixの規則では、EXIT_SUCCESS
を0として定義します(EXIT_FAILURE
を1として定義します)。実際、それは標準です C規約 !
したがって、Posixシェルおよび exit(2) syscallsの場合、0は「成功」を意味し、直観的にはfalseよりもtrueです。
特に、シェルのif
は、 "then"ブランチに続くプロセスにEXIT_SUCCESS
(つまり0)を返したいと思っています!
Schemeでは(ただしCommon LISPでも [〜#〜] melt [〜#〜] )でもない0とnil(つまり、Schemeでは()
)はtrueです。は#f
です
私は同意します、私はつまらないです!
1とtrueの間の対応は、いくつかの数学的特性によって必要とされることを示唆する多くの回答があります。私はそのような特性を見つけることができず、それが純粋に歴史的な慣習であることを示唆しています。
2つの要素を持つフィールドを指定すると、加算と乗算の2つの演算があります。このフィールドにブール演算をマッピングするには、次の2つの方法があります。
従来、Trueは1で識別され、Falseは0で識別されます。ANDは*で識別され、XORは+で識別されます。したがって、ORは飽和加算です。
ただし、Trueを0で、Falseを1で簡単に識別できます。次に、ORを*で、XNORを+で識別します。したがって、ANDは飽和加算です。
Cは、ハードウェアに近い低レベルのプログラミングに使用されます。この領域では、同じデータに対してビット演算と論理演算を切り替える必要がある場合があります。テストを実行するためだけに数値式をブール値に変換する必要があるため、コードが煩雑になります。
次のようなものを書くことができます:
if (modemctrl & MCTRL_CD) {
/* carrier detect is on */
}
のではなく
if ((modemctrl & MCTRL_CD) != 0) {
/* carrier detect is on */
}
ある孤立した例ではそれほど悪くはありませんが、そうする必要があると厄介になります。
同様に、操作を逆にします。比較などのブール演算の結果が0または1を生成するのに役立ちます。modemctrl
にキャリア検出ビットがあるかどうかに基づいて、Wordの3番目のビットを設定するとします。
flags |= ((modemctrl & MCTRL_CD) != 0) << 2;
ここでは、!= 0
、双方向ワイズの結果を減らす&
式から0
または1
、しかし結果は単なる整数であるため、ブール値を整数にさらに変換するために煩わしいキャストを追加する必要がなくなります。
現在のCはbool
型を持っていますが、それでもこのようなコードの有効性は保持されます。これは、それが良いことであり、そうでない場合は後方互換性による大規模な破損が原因です。
Cが巧妙な別の例:2つのブール条件を4方向スイッチとしてテストする:
switch (foo << 1 | bar) { /* foo and bar booleans are 0 or 1 */
case 0: /* !foo && !bar */
break;
case 1: /* !foo && bar */
break;
case 2: /* foo && !bar */
break;
case 3: /* foo && bar */
break;
}
これをCプログラマーから遠ざけることはできません。
最後に、Cは時々一種の高レベルのアセンブリ言語として機能します。アセンブリ言語では、ブール型もありません。ブール値は、メモリ位置またはレジスタ内のビットまたはゼロ対ゼロ以外の値です。整数ゼロ、ブールゼロ、アドレスゼロはすべて、アセンブリ言語の命令セット(および浮動小数点ゼロ)でも同じようにテストされます。 Cとアセンブリ言語の間の類似性は、たとえば、Cが別の言語をコンパイルするためのターゲット言語として使用されている場合に役立ちます(強く型付けされたブール値を持つ言語でも!)
ブール値または真理値には2つの値しかありません。真と偽。
これらはnotは整数として表されますが、ビット(0および1)として表されます。
0または1の横にある他の整数がfalseではないと言うのは、混乱を招くステートメントです。真理値表は、整数ではなく真理値を扱います。
真理値の見込みから、-1または2はすべての真理値表とそれらに関連するブール論理をすべて破壊します。
ほとんどの言語には通常、boolean
タイプがあり、整数などの数値タイプにキャストすると、falseが整数値0としてキャストされることが明らかになります。