Not only is the Internet dead, it's starting to smell really bad.:2021年04月17日分

2021/04/17(Sat)

[プログラミング] gdtoaでレッツprintf(3)実装(その6)

前回のa変換についてもコード整理したものを ここに貼っといた、幅指定によるスペース埋めそしてゼロ埋め(0)そして左寄せ(-)書式指定子の実装もつっこんだので若干読みづらいコードになったけど(それでもNのvfwprintf.c読むよかマシだと思う)より実践的なコードになったと思う。

あわせて他のe/f/g変換も対応してあるので、こんなもんに興味のある奇人は ここ見てね。

なんかディアゴスティーニの週刊printf(3)、毎週送られてくるコードを組み合わせるとオレオレprintf(3)が完成みたいなノリになってきたけど、初巻900円以降続刊は1800円で数年後の100巻目でprintf(3)がようやく完成とか誰が買うのそんな雑誌。

つーか今回でgdtoaの使い方についてはおしまいなのだけど、本当にprintf(3)作る気ならlong doubleサポートのためにldtoa/hldtoa周りも書かねばならんのだ、ほんとprintf(3)ってそびクソだなぁ…

今回のコードで追加した部分、幅指定の実装の説明だけしとくか、dtoa/hdtoaの返す結果から最終的に組み上がる文字列の長さの計算ルーチン。

f変換の場合は以下の通り。

static inline int
cvt_fsize(int sign, int len, int prec, int exp, int flags)
{
	int s, i, f, d;

	s = (sign) ? 1 : 0;
	i = (exp <= 0) ? 1 : exp;
	f = (exp > len) ? 0 : len - exp;
	if (f < prec)
		f = prec;
	d = (f > 0 || flags & SHARP) ? 1 : 0;
	return s + i + f + d;
}

s(ign part)が符号、i(nteger part)が整数部、f(ractional part)が小数部、d(ecimal point)が小数点、それぞれの長さの計算方法ね。 その2で表まで組んでexpの値について説明してあるから理解してれば判るとは思うけど、ざっくりまとめると

ってとこですかね、ああこれ典型的なソースのコメントに「iに1を足す」って書くやつだ…

もちろんLC_NUMERIC対応を実装すればさらにここに桁区切り文字の長さも加わってくるのだけど、今回はここまで。

e変換の場合はこちら。

static inline int
cvt_esize(int sign, int len, int prec, int exp, int flags)
{
	int s, i, f, d, e;

	s = (sign) ? 1 : 0;
	i = 1;
	f = len - 1;
	if (f < prec)
		f = prec;
	d = (f > 0 || flags & SHARP) ? 1 : 0;
	if (exp < 0)
		exp = -exp;
#if 0
	e = 2;
	if (exp > 9) {
		do {
			++e;
			exp /= 9;
		} while (exp > 9);
		++e;
	} else {
		e += 2;
	}
#else
	/* assume DBL_MIN_EXP(-1021) DBL_MAX_EXP(1024) */
	if (exp < 100)
		e = 4;
	else if (exp < 1000)
		e = 5;
	else
		e = 6;
#endif
	return s + i + f + d + e;
}

e(notation)が指数部の計算、こちらもざっとまとめると

最後にa変換。

static inline int
cvt_asize(int sign, int len, int prec, int exp, int flags)
{
	int x, s, i, f, d, p;

	x = 2;
	s = (sign) ? 1 : 0;
	i = 1;
	f = len - 1;
	if (f < prec)
		f = prec;
	d = (f > 0 || flags & SHARP) ? 1 : 0;
	if (exp < 0)
		exp = -exp;
#if 0
	p = 2;
	while (exp > 9) {
		++p;
		exp /= 9;
	}
	++p;
#else
	/* assume DBL_MIN_10_EXP(-307) DBL_MAX_10_EXP(308) */
	if (exp < 10)
		p = 3;
	else if (exp < 100)
		p = 4;
	else
		p = 5;
#endif
	return x + s + i + f + d + p;
}

xは最初の0xの長さ、p(notation)が指数部の計算、e変換とほとんど一緒だけど

ちゅーとこに注意。

んでrpad/lpad関数でフラグが立ってれば左右どちらかをスペースあるいはゼロで埋めるって寸法よ。

#define PADSIZ	20
static const char zeros[PADSIZ] = "00000000000000000000";
static const char spaces[PADSIZ] = "                    ";

static inline int
pad(const char *filler, int len, FILE *fp)
{
	while (len > PADSIZ) {
		if (fwrite(filler, 1, PADSIZ, fp) != PADSIZ)
			return 1;
		len -= PADSIZ;
	}
	if (fwrite(filler, 1, len, fp) != len)
		return 1;
	return 0;
}

static inline int
lpad(int sign, int width, int siz, int flags, FILE *fp)
{
	if (width > siz && (flags & (MINUS|ZERO)) == 0 &&
	    pad(spaces, width - siz, fp))
		return 1;
	if (sign && putc(sign, fp) == EOF)
		return 1;
	if ((flags & HEX) && fwrite("0x", 1, 2, fp) != 2)
		return 1;
	if (width > siz && (flags & (MINUS|ZERO)) == ZERO &&
	    pad(zeros, width - siz, fp))
		return 1;
	return 0;
}

static inline int
rpad(int width, int siz, int flags, FILE *fp)
{
	if (width > siz && (flags & MINUS) &&
	    pad(spaces, width - siz, fp))
		return 1;
	return 0;
}

static inline int
cvt_afmt(char *head, int len, int width, int prec, int exp, int flags, FILE *fp)
{
	int sign, size;

	sign = cvt_sign(flags);
	size = cvt_asize(sign, len, prec, exp, flags);
	if (lpad(sign, width, size, flags|HEX, fp))
		return 1;
...
	if (rpad(width, size, flags, fp))
		return 1;
	return 0;
}

左埋めの場合スペースとゼロで符号および0xの出現位置が変わるので、lpad関数の中で出力してしまう。

コードをシンプルにすべく条件分岐が若干非効率になっとるけど、そもそもprintf(3)自体が不経済なシロモノなので些細な性能問題とかは無視する。 最適化なんぞコンパイラがやってくれんだろ(鼻ホジ)。

以前に古いNのprintf(3)実装のプロファイルをとった時、整数型のlやll書式指定子を実装するのにいちいちint/long/long longでコード別にしたくないからintmax_tで統一とかやらかしとって、そこで性能ガタ落ちしてた記憶があるな。 今はすべて別関数になっとるけど。

話は変わるが、今のglibcにはI書式指定子という拡張があって整数型(i/d/u)の場合0-9をlocaleの代替文字列で出力するなんて機能あるんだけど、これLC_NUMERICにはそんなデータ持ってるはずないんだけどもlocaledef拡張したんかな。 まさかLC_TIMEのALT_DIGITを使うわけにはいかないだろうし。

まぁLinuxインストールして確認すりゃいいんだろけど最近はVMwareですら億劫なのでやる気にならん、もう何もかもがめんどくさいしWSL2とかもっとアレ。