はじまり
いつものようにTwitterを見ていると、気になるツイートがありました。
A=B、A=Cであるとき必ずしもB=Cとはならないとか思ってる人間が、その謎理論で反論リプしてくる世界で人類がわかり合うことはないんだなって思う pic.twitter.com/lsoS09jcBT
— ぼのぼの@煙草と柚子とヨーグルト (@BonoBono1031) 2019年6月9日
a=bかつb=cなら
— ЯXY (@Necroxis_Girl) 2019年6月9日
a=cが成り立つんだよなあ
A=B、A=CであるときB=Cになるとは限らないと思ってる世界で生きてるの日常生活に支障をきたしてそう
— ぼのぼの@煙草と柚子とヨーグルト (@BonoBono1031) 2019年6月9日
なんか別に間違ってなくね??みたいな人がいるみたいでビビる。A=B、B=Cなら絶対にA=Cだし、こいつが出してる例は野菜⊃ニンジン、ニンジン=オレンジ、野菜⊃オレンジって話でそもそも=の例として不適切だから間違ってないように見えるだけ、中学数学ですよ
— ぼのぼの@煙草と柚子とヨーグルト (@BonoBono1031) 2019年6月9日
「A=B、B=Cなら絶対にA=C」という性質は、「推移律」といいます。
この性質が成り立つかどうかは、「=」の定義によります。
A=B、A=Cであっても、B=Cとならない例
C言語では、「=」は代入演算子です。
代入演算子では、右辺のデータが左辺の型に変換され、変換後の値に評価されます。
この性質を利用することで、A=B
およびA=C
は真だけど、B=C
は偽となる例を作ることができます。
#include <stdio.h>
int main(void) {
int A = 0;
char B = 128;
int C = 256;
if (A=B) puts("A=B");
if (A=C) puts("A=C");
if (B=C) puts("B=C");
return 0;
}
このコードを実行すると、
A=B
A=C
と出力されました。
この環境では、256をchar
型に変換すると0になるため、B=C
は偽となりました。
同様に、以下にA=B
、B=C
だけどA=C
ではない例を示します。
#include <stdio.h>
int main(void) {
char A = 1;
int B = 1;
int C = 256;
if (A=B) puts("A=B");
if (B=C) puts("B=C");
if (A=C) puts("A=C");
return 0;
}
このコードを実行すると、
A=B
B=C
と出力されました。
A==B、A==Cであっても、B==Cとならない例
先ほどの例は、「知らない人が見ると等価と誤解してしまう代入演算子」を用いており、
副作用もあるため、あまり美しくない例でした。
今度は、推移律の成り立たない等価比較演算子の例を示します。
それは、JavaScriptの==
演算子です。
(ここではJavaScriptとしてSpiderMonkeyを扱います)
var A = 0;
var B = "";
var C = "0";
if (A==B) print("A==B");
if (A==C) print("A==C");
if (B==C) print("B==C");
このコードを実行すると、
A==B
A==C
と出力されました。
同様に、以下にA==B
、B==C
だけどA==C
ではない例を示します。
var A = "";
var B = 0;
var C = "0";
if (A==B) print("A==B");
if (B==C) print("B==C");
if (A==C) print("A==C");
このコードをhttps://ideone.com/gQAnM5すると、
A==B
B==C
と出力されました。
比較演算子 - JavaScript | MDN
によると、
標準的な等価演算子 (== と !=) は 2 つのオペランドの比較に抽象的等価比較アルゴリズムを使用します。
とあります。この「抽象的等価比較アルゴリズム」は、ECMAScriptの仕様書の
11.9.3 The Abstract Equality Comparison Algorithm
で定義されており、今回の例に関わる部分は
1. If Type(x) is the same as Type(y), then (中略) c. If Type(x) is Number, then (中略) iii. If x is the same Number value as y, return true. (中略) d. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false. (中略) (中略) 2. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y). 3. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
となっています。
これに従うと、""=="0"
はString同士の比較なので1のルールを使用し、文字の並びが等しくないのでfalseになります。
また、ToNumber("")
もToNumber("0")
も0になるため、
0==""
、0=="0"
、""==0
、"0"==0
は、いずれも2や3のルールを使用した後、1のルールを使用し、trueになります。
ToNumberの定義は9.3.1 ToNumber Applied to the String Typeにあり、
The MV of StringNumericLiteral ::: [empty] is 0.
The MV of DecimalDigit ::: 0 or of HexDigit ::: 0 is 0.
となっています。
A==B、A==Cであっても、B==Cとならない別の例
C++のstd::string
(std::basic_string<char>
)とconst char*
との==
演算子は、文字列を比較します。
一方、const char*
同士の==
演算子は、ポインタの値を比較します。
この性質を用いて、以下のように、A==B
およびA==C
は真だけどB==C
は偽となる例を作ることができます。
#include <iostream>
#include <string>
int main(void) {
const char* B = "hoge\0hoge";
const char* C = B + 5;
std::string A = B;
if (A==B) std::cout << "A==B\n";
if (A==C) std::cout << "A==C\n";
if (B==C) std::cout << "B==C\n";
return 0;
}
このコードを実行すると、
A==B
A==C
と出力されました。
同様に、以下にA==B
、B==C
だけどA==C
ではない例を示します。
#include <iostream>
#include <string>
int main(void) {
const char* A = "hoge\0hoge";
std::string B = A;
const char* C = A + 5;
if (A==B) std::cout << "A==B\n";
if (B==C) std::cout << "B==C\n";
if (A==C) std::cout << "A==C\n";
return 0;
}
このコードを実行すると、
A==B
B==C
と出力されました。
まとめ
=
は等価比較演算子だとは限りません。
等価比較演算子は、推移律(「A==B
かつB==C
」ならば「A==C
」)が成り立つとは限りません。