ときどきの雑記帖 RE* (新南口)
真と偽と
true/false
世界のプログラミング言語(29) 真偽型TrueはFalseより大きい?主要言語の歴史と共に比較してみよう | マイナビニュース
という記事があって、そこでは Perl (5) true < false 1987年
となっているんだけど、これどうやって確かめたんだろう?
そして、偽が真よりも大きくなる言語には、PerlとVBA/VBといった言語があります。Perlの登場は1987年、Visual Basicの登場は1991年ですが、 これらの言語に共通するのは、熱心のファンが多いことが思い浮かびます。
VBScriptやVBAでTrueとFalseの値を表示させてみると、Trueが-1、Falseが0と表示されました。 Perl5ではtrueやfalseの値は数値として表示されませんでしたが、結果から考えると内部的にVBと同じように定義されているのでしょう。 どの言語でも基本的に0が偽なのは共通であり、Trueを1とするか-1とするかが異なるようです。
手元でこんな感じで確かめても true が -1 だとは思えないんだけど (ご存知の通り perlでは変数には sigil がつくし、予約語にも true/falseはないので そのまま書くと bareword ということで文字列と同じ扱いになる)。
>perl -e "print((true < false)+1)"
Unterminated <> operator at -e line 1.
>perl -e "print((true > false)+1)"
1
>perl -e "print(true gt false)"
1
>perl -e "print(true lt false)"
>perl -e "print((true lt false)+0)"
0
>perl -e "print((true lt false)+0)"
0
>perl -e "print(true gt false)"
1
>perl -e "print((true gt false)+0)"
1
>perl -e "print((true gt false)+1)"
2
>perl -e "print((true lt false)+1)"
1
ちょっとひねったやり方で試してもtrueは1っぽい。
>perl -e "print(1>0)"
1
>perl -e "print((1>0)==0)"
>perl -e "print((1<0)==0)"
1
>perl -e "print((1>0)==1)"
1
>perl -e "print((1<0)==1)"
ということで、ソースに当たる。 最初に最近のバージョンを見たんだけどちょっと複雑になっていたので昔のものから。
perl5/pp.c at perl-5.000 · Perl/perl5
PP(pp_lt)
{
dSP; tryAMAGICbinSET(lt,0);
{
dPOPnv;
SETs((TOPn < value) ? &sv_yes : &sv_no);
RETURN;
}
}
PP(pp_gt)
{
dSP; tryAMAGICbinSET(gt,0);
{
dPOPnv;
SETs((TOPn > value) ? &sv_yes : &sv_no);
RETURN;
}
}
実際にはこの後ろに ge やら ne やら cmp やらが続いているけど略。
ここで値のセットに使われているsv_yes や sv_no がどういうものかというとこうで、
perl5/perl.c at perl-5.000 · Perl/perl5
sv_setpv(&sv_no,No);
SvNV(&sv_no);
SvREADONLY_on(&sv_no);
sv_setpv(&sv_yes,Yes);
SvNV(&sv_yes);
SvREADONLY_on(&sv_yes);
さらに Yes と No はと言えば
perl5/perl.h at perl-5.000 · Perl/perl5
/* handy constants */
EXT char * Yes INIT("1");
EXT char * No INIT("");
なんだけど(Noは"0"じゃなくて空文字列なのね。数値コンテキストで評価すると0になるということなんだろう)、 なんのことはない。こんな定義があったのだった(笑い
perl5/handy.h at perl-5.000 · Perl/perl5
#ifdef TRUE
#undef TRUE
#endif
#ifdef FALSE
#undef FALSE
#endif
#define TRUE (1)
#define FALSE (0)
さらにわかりやすい古いバージョンから(メジャーバージョンが上がってこの辺の扱いが変わることはそうそうないでしょう) 抜き出してみる。。
perl5/eval.c at perl-4.0.36 · Perl/perl5
case O_LT:
value = str_gnum(st[1]);
value = (value < str_gnum(st[2])) ? 1.0 : 0.0;
goto donumset;
case O_GT:
value = str_gnum(st[1]);
value = (value > str_gnum(st[2])) ? 1.0 : 0.0;
goto donumset;
case O_LE:
value = str_gnum(st[1]);
value = (value <= str_gnum(st[2])) ? 1.0 : 0.0;
goto donumset;
case O_GE:
value = str_gnum(st[1]);
value = (value >= str_gnum(st[2])) ? 1.0 : 0.0;
goto donumset;
case O_EQ:
if (dowarn) {
if ((!st[1]->str_nok && !looks_like_number(st[1])) ||
(!st[2]->str_nok && !looks_like_number(st[2])) )
warn("Possible use of == on string value");
}
value = str_gnum(st[1]);
value = (value == str_gnum(st[2])) ? 1.0 : 0.0;
goto donumset;
case O_NE:
value = str_gnum(st[1]);
value = (value != str_gnum(st[2])) ? 1.0 : 0.0;
goto donumset;
case O_NCMP:
value = str_gnum(st[1]);
value -= str_gnum(st[2]);
if (value > 0.0)
value = 1.0;
else if (value < 0.0)
value = -1.0;
goto donumset;
case O_BIT_AND:
なるほど整数じゃなくて、doubleの1と0を使ってたと。
ついでに1.0からも。
perl5/arg.c at perl-1.0.16 · Perl/perl5
case O_LT:
value = str_gnum(sarg[1]);
value = (double)(value < str_gnum(sarg[2]));
goto donumset;
case O_GT:
value = str_gnum(sarg[1]);
value = (double)(value > str_gnum(sarg[2]));
goto donumset;
case O_LE:
value = str_gnum(sarg[1]);
value = (double)(value <= str_gnum(sarg[2]));
goto donumset;
case O_GE:
value = str_gnum(sarg[1]);
value = (double)(value >= str_gnum(sarg[2]));
goto donumset;
case O_EQ:
value = str_gnum(sarg[1]);
value = (double)(value == str_gnum(sarg[2]));
goto donumset;
case O_NE:
value = str_gnum(sarg[1]);
value = (double)(value != str_gnum(sarg[2]));
goto donumset;
case O_BIT_AND:
最初から(doubleと整数の違いはあるとはいえ)今と変わらんということですな。
TINY BASIC
おまけで BASICも。 ちょっと前にソースが公開されたGW-BASICはTRUE が -1でFALSEが0だけど、 Palo Alto TINY BASIC では TRUEが 1だった。 詳しくは以下の通り。
EXPR1: LXI H,TAB8-1 ;LOOKUP REL.OP.
JMP EXEC ;GO DO IT
XP11: CALL XP18 ;REL.OP.">="
RC ;NO, RETURN HL=0
MOV L,A ;YES, RETURN HL=1
RET
XP12: CALL XP18 ;REL.OP."#"
RZ ;FALSE, RETURN HL=0
MOV L,A ;TRUE, RETURN HL=1
RET
XP13: CALL XP18 ;REL.OP.">"
RZ ;FALSE
RC ;ALSO FALSE, HL=0
MOV L,A ;TRUE, HL=1
RET
XP14: CALL XP18 ;REL.OP."<="
MOV L,A ;SET HL=1
RZ ;REL. TRUE, RETURN
RC
MOV L,H ;ELSE SET HL=0
RET
XP15: CALL XP18 ;REL.OP."="
RNZ ;FALSE, RETURN HL=0
MOV L,A ;ELSE SET HL=1
RET
XP16: CALL XP18 ;REL.OP."<"
RNC ;FALSE, RETURN HL=0
MOV L,A ;ELSE SET HL=1
RET
XP17: POP H ;NOT .REL.OP
RET ;RETURN HL=<EXPR2>
XP18: MOV A,C ;SUBROUTINE FOR ALL
POP H ;REL.OP.'S
POP B
PUSH H ;REVERSE TOP OF STACK
PUSH B
MOV C,A
CALL EXPR2 ;GET 2ND <EXPR2>
XCHG ;VALUE IN DE NOW
XTHL ;1ST <EXPR2> IN HL
CALL CKHLDE ;COMPARE 1ST WITH 2ND
POP D ;RESTORE TEXT POINTER
LXI H,0 ;SET HL=0, A=1
MVI A,1
RET
ANSIやらJISでは(ISO、はなかったような)なんか決まっていたんだろうかこの辺。
GW-BASIC
浮動小数点数の比較や文字列の比較は省略して、整数比較の部分から。
GW-BASIC/MATH1.ASM at master · microsoft/GW-BASIC
ICOMP: ;COMPARE (DX) AND (BX)
;(AL)=1 IF (DX) .LT. (BX)
;(AL)=0 IF (DX) = (BX)
;(AL)=-1 IF (DX) .GT. (BX)
MOV AX,BX ;SO WE CAN HAVE SEPARATE ENTRY FOR AX
ICMPA: SUB AX,DX ;COMPARISONS
JZ IC40 ;ALL OK , JUST EXIT
JO IC20 ;IF SF=1 ADDITIONALLY THEN AX LARGER
JS IC30 ;DX DEFINITELY LARGER
IC10: XOR AL,AL ;(AX) LARGER
INRART: INC AL ;(AL)=1
IC15: RET
IC20: JS IC10
IC30: STC
SBB AL,AL ;(AL)=377
IC40: RET
追記
上記の部分だけでは説明が足りていなくて、この関数からALレジスター経由で返される値は コメントにもある通り -1, 0, 1 の三種類。そしてこれを
GW-BASIC/GWEVAL.ASM at master · microsoft/GW-BASIC
SUBTTL MORE FORMULA EVALUATION - LOGICAL, RELATIONAL OPS
DOCMP: INC AL ;SETUP BITS
ADC AL,AL ;4=LESS 2=EQUAL 1=GREATER
POP CX ;WHAT DID HE WANT?
AND AL,CH ;ANY BITS MATCH?
ADD AL,LOW 255 ;MAP 0 TO 0
SBB AL,AL ;AND ALL OTHERS TO 377
CALL CONIA ;CONVERT [A] TO AN INTEGER SIGNED
JMP SHORT RETAPG ;RETURN FROM OPERATOR APPLICATION
NOTER: MOV DH,LOW 90 ;"NOT" HAS PRECEDENCE 90, SO
CALL LPOPER ;FORMULA EVALUATION IS ENTERED WITH A DUMMY
;ENTRY OF 90 ON THE STACK
CALL FRCINT ;COERCE THE ARGUMENT TO INTEGER
INS86 367,323 ;NOT [H,L]
MOV FACLO,BX ;UPDATE THE FAC
POP CX ;FRMEVL, AFTER SEEING THE PRECEDENCE
;OF 90 THINKS IT IS APPLYING AN OPERATOR
;SO IT HAS THE TEXT POINTER IN TEMP2 SO
RETAPG: JMP RETAOP ;RETURN TO REFETCH IT
で、0 と -1 に変換している。
この辺りはアドレステーブルを駆使していて呼び出しのフローを 説明する(追いかける)のがちょっと大変。