■_
わたしもしらないぬるぽのせかい
あ、先に書かれてた。
C/C++においてNULLと0に関して云えることは、
「NULLの定義は0とか (void *)0 (こっちはC++の場合はありえない。後述)
であるとは限らないが、整定数 0(0Lを含む)を
ポインタとして評価すると
それは常にナルポインタである」
ということだと思います。参考→
C FAQ 5
odz buffer - NULL と 0
で、odzさんは
ええと、C++ では NULL と 0 が同一なんて規定があったっけ? NULL マクロを使うのをやめてリテラルの 0 を使えという話はありがちだけど、(後略)
と書かれているわけですが、
Stroustrup: C++ Style and Technique FAQによると
Should I use NULL or 0?
In C++, the definition of NULL is 0, so there is only an aesthetic
difference. I prefer to avoid macros, so I use 0. Another problem with
NULL is that people sometimes mistakenly believe that it is different
from 0 and/or not an integer. In pre-standard code, NULL was/is
sometimes defined to something unsuitable and therefore had/has to be
avoided. That's less common these days.
If you have to name the null pointer, call it nullptr; that's what it's
going to be called in C++0x. Then, "nullptr" will be a keyword.
あの検索性の悪いJISのpdfから探す気にはなれないので、
「規格」としてはどーなのよ? という質問に対する答えはわかりませんが、
Stroustrup氏の意向としては「定義」と考えているのかもしれません。
まあ #define NULL 0だからといって即
「NULL と 0 は同一である」とは云えないのはC FAQにある通りですね。
で、すっきりさせるために nullptr なるものを持ち込むと。
C++でのNULLのdefineがCのような
(void *)0ではなく、単なる整定数 0になっているのは、
void *からの暗黙のキャストを許さなくなっているから。
たとえば
#ifdef CSTYLE
#define NULL (void *)0
#else
#define NULL 0
#endif
int
main()
{
char *p;
p = NULL;
return 0;
}
というプログラムをC++プログラムとしてコンパイルすると、
NULL が (void *)0のとき、コンパイルエラーになる。
Cでも NULL が 整定数 0であってもいいのだけど、
あえて (void *)0とかになっているのは
C FAQにある通り。
まあこの辺は odzさんあたりなら先刻ご承知だと思うので
はっきり云って蛇足っす。
上の定義は、ポインターの型の扱いに関して間違いの あるプログラムを動くようにするし
(ポインターの内部表現がどんな データ型でも同じマシンに限られる。そういう意味で
役に立つとはちょっといいにくいが)、この定義により、NULLをポインターの意味以外で
使う間違いを見つけることができるかもしれない(たとえばASCIIのナル文字(NUL)が
本当は必要な場合など。質問5.9参照)。
■_
わたし売ります
ベイスターズからフリーエージェント宣言していた門倉投手ですが、
まあジャイアンツに行き先は落ち着いたらしいと。
mixi のベイスターズコミュでは大いに盛り上がったようですが、
彼に対して厳しい味方をする人が多かったように感じます。
球団が決めた年俸の額に逆らっちゃいけないという決まりはないし、
プロ野球選手、それも投手の選手寿命なんて短いのだから
彼が年俸もっとくれと主張するのには何のケチもつけるつもりはない。
がしかし、彼が例えば(ジャイアンツが提示したという)年俸一億円の
働きをする選手かどうかと問われれば、「ないんじゃなかろうか」
と答えるでしょう。ただでさえベイスターズはフリーエージェント宣言した
選手の引き留めで痛い目見てて(斎藤隆とか鈴木尚典とか)、
赤字に苦しんでたりしてそうそう大盤振る舞いはできない事情もある。
まあジャイアンツが複数年(2年)、一年あたり一億という提示をしてくれた
らしいので良かったね。と。
いかん、例によってオチなかった。
■_
リスト内包表記 → 内包リスト
Floating Log 10.12.2006
長くなりそうなので、先にまとめを言ってしまう:
「リスト内包表記」の代わりに「内包リスト」と言おう。
Fe2+さんも訳語で悩んでるんですねえ。
RSSリーダーの調子が悪かったのか
八百万の神々には今まで気が付きませんでした。
ま、それはともかく。
いいんじゃないでしょうか、「内包リスト」
細かいこというと「ナニを内包しているので?」
という感想を持たないでもないのですが、
そういうこと云い出すとまた語が長くなってしまうので引っ込めます。
で、
結局「実践Python」は買わなかった。
わは。
■_
7021 != 7021
わかりやすく、かつ簡潔に書くのは難しい。
二進と十進の間
当然 (?) ながら、awk も同じ挙動ですね。
$ gawk 'BEGIN{printf("%d\n", 70.21*10000)}'
702099
私に説明できるだけの能力がないので、コレ以降のツッコミはしません。
斉藤さんに敬意を表してawk(gawk)を使って話を進めましょう。
まずは次のスクリプトを実行してみます。
BEGIN {
spam = 70.21 * 10000
egg = 70.21 * 100
ham = 702100
printf("%d:%d:%d\n", spam, egg, ham)
if (spam == ham)
print "spam equal ham"
else
print "spam not equal ham"
}
どんな結果になると思いますか?
(正解を知りたい人は以下の塗りつぶし部分を選択して反転表示させて確認してください)
E:\work\script\awk>gawk -f moge.awk
702099:7020:702100
spam not equal ham
予想通りでしたか?
このような結果になった理由は、
第一に小数付きの数値を十進数→二進数に変換したときに発生する誤差、
第二に浮動小数点付きデータを特定の桁数に収めるために丸めたときに発生する誤差
の二つが挙げられます。
一般的に云ってコンピュータでは小数付きのデータを
浮動小数点数として格納し、扱います
(→ 浮動小数点数 - Wikipedia)が、
今日広く使われている IEEE 754 という形式では、
2x × 1.yyy...
というように基数として2を使用して小数つきの数を表します。
したがって、どんな数値であっても二進数で表されるということです。
さてここで70.21という数値に注目してみましょう。
この数字を二進数で表すとどうなるでしょうか?
整数部に関しては話は簡単で、
70 =26 + 22 + 21 になります。
小数部はどうするかというと、
話は同じことで数直線上で見ると理解しやすいと思いますが
2-1, 2-2, 2-3の和で表します。
詳しい説明は省きますが、小数部に2を掛けてその答えが1を超えたら
対応するビットが1(さらに積から1を引いて計算を続行)、
そうでないならビットはゼロという操作を値がゼロになるまで続けることで
求めることができます。
さっそく0.21でやってみましょう。
0.21 × 2 → 0.42 … 0
0.42 × 2 → 0.84 … 0
0.84 × 2 → 1.68 … 1
0.68 × 2 → 1.36 … 1
0.36 × 2 → 0.72 … 0
0.72 × 2 → 1.44 … 0
0.44 × 2 → 0.88 … 0
0.88 × 2 → 1.76 … 1
0.76 × 2 → 0.52 … 0
0.52 × 2 → 1.04 … 1
0.04 × 2 → 0.08 … 0
0.08 × 2 → 0.16 … 0
0.16 × 2 → 0.32 … 0
0.32 × 2 → 0.64 … 0
0.64 × 2 → 1.28 … 1
0.28 × 2 → 0.56 … 0
0.56 × 2 → 1.12 … 0
終わる気配がありません。
人の手でやるのもバカらしいので、これもawkにやらせましょう。
BEGIN {
val = 21 # 0.21 * 100 なぜ0.21そのままでやらないのかは宿題です :-)
limit = 1000 # お好きな数字をどうぞ
for (i=0; val != 0 && i<limit; i++) {
val *= 2
if (val >= 100) {
printf "1"
val -= 100
}
else {
printf "0"
}
}
if (val != 0)
printf "...まだ続きます"
}
1000桁やっても止まりません。
IEEE754 の倍精度(awkやPHPで浮動小数点数を表すのに使われる型)では
52+1 ビット分しか最初の例で挙げた 1.yyy の部分を表すのに使うことができないので、
1000桁やっても止まらない 70.21 を
正確に表すことはできません
つまりここで誤差が発生するわけです。
awkではちょっと厳しいのでCの力を借りて、
どういう値で格納されたのか調べてみましょう。
#include <stdio.h>
#include <math.h>
char *tbl[] = {
"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111",
};
int
main()
{
double val = 70.21;
double tval;
char *p;
int exponent;
char mantissa[64] = {0};
int i,j;
p = (char *)&val;
exponent = (p[7] & 0x7f) * 16 + ((p[6] >> 4) & 0xf) - 1023;
strcpy(&mantissa[ 0], tbl[p[6]&0xf]);
for (i=5, j=0; i>=0; i--) {
strcpy(&mantissa[j+4], tbl[(p[i] >> 4)&0xf]);
strcpy(&mantissa[j+8], tbl[p[i]&0xf]);
j+=8;
}
printf("%5.2f = %c2^%d * 1.%s\n",
val,
(p[7] & 0x80) ? '-' : ' ',
exponent, mantissa);
for (tval=0, p=mantissa+strlen(mantissa); p>=mantissa; p--)
tval = ((tval / 2) + (*p=='1' ? 1 : 0));
tval = tval/2 + 1;
tval *= pow(2, exponent);
printf("%18.15f\n", tval);
return 0;
}
//注意: 手抜きで0を特別扱いしていませんので使いまわしをするときは気をつけて
//バイトオーダーがビッグエンディアンのマシンでは正常動作しません
//実行結果
//70.21 = 2^6 * 1.0001100011010111000010100011110101110000101000111101
//70.209999999999994
やはり変換時に誤差が出ていますね。
gawk 'BEGIN{printf("%d\n", 70.21*10000)}'
でprintfの対象になっている数値は実はこの 70.2099... に10000を乗じて
それを整数化したものなのです。
つまり
これはPHPに限った問題ではないが、コンピュータ・プログラムは内部で2進数演算
しているため、 10進数の計算と比べると誤差が発生することがある。とくに、浮動
小数を整数化するときや、四捨五入をする際に問題になることが多い。
二進数で計算しているから誤差が出る、十進数で計算しているから誤差が出ない
のではないのです。
誤差が出るような計算を行えば、二進でやろうが十進でやろうが
はたまた十六進でやっても誤差は出ます
(これは今述べた誤差とは性質が違うものなので本当は同列に並べて話をしてはいけない)
む。BCMathとやらを調べる時間がなくなってしまった。
続く。
かもしれない。
■_
マジっすか?
Mac OS Xの文字コード問題に関するメモ - リムネット(RIMNET)のメール・アドレスにUTF-8のメールが届かない件
以前、リムネットのアドレス宛のメール(charset=UTF-8)でおもしろい文字化け
(やぎさん郵便(Apple Mail版))が見られた。そこで、検証のために再度UTF-8
のメールを送ったところ、これが届かない。charset=ISO-2022-JPのメールなら届く
ので、おそらくサーバ側の迷惑メール・フィルタの類で弾かれているものと思われ
る。文字化けメールを送ったので、迷惑メール・フィルタに何らかの形で「学習」
されてしまったのだろうか。
ん、でも自分で自分にUTF-8のメールを送ったことがあるけどそれは届いたような?
まあ時期的に微妙だったかもしれない。
要チェック。
■_
単なる仮説ですが
ところで、memset の第二引数って、何で int なんだろう。1 バイト = sizeof(char)
= sizeof(unsiged char) の保証が無いから?
K&R時代のルールで、昇格ルールに従ってchar を渡そうとしても
int に拡幅されて渡されるようになっていたからではないかと。
あと、ふつう(笑)のCPUだと int より小さい幅の数値をスタックに積むことが
できないからというのもあるかな
(int = 8bitなんて環境とか、6502 みたいなCPUは無視(笑))。
signed/unsigned で大きさが変わるというのはちょっと考えづらい。
世の中は広いのであるかもしれないけど。