【程设大作业】printf 的实现

我决定挂(biao)一挂(biao)我们的这个程设大作业。
(同样是大一,别人家的大作业是写一个 jumping game,怎么到你这就是个 printf 呢。。。

Task

  一句话,就是要手写 printf。
  具体来讲,你需要自己实现一个函数(C 语言),名叫 myprintf,其功能和 printf 一致——参数第一个是字符串format[],后面是任意个参数,然后能把这些东西输出出来,返回值是一共输出了多少个字符。

  当然你不能在 myprintf 里内嵌 printf,只能用 putchar。
  为了简化问题,对于 % 后面的转换字符只作部分规定(见下表),其他如 %lld、%a 等视为未定义。下面是题目的部分pdf:

考点

  第一自然是可变参列表的使用了。
  第二就是对format这个字符串的处理。
  第三就是如何输出这些参数

  隐藏的第四点是你要发现 printf 的所有隐藏鬼畜

sol

可变参列表

  头文件是:

1
#include<stdarg.h>

  参数数量、类型是不定的,故曰可变参列表,用三个点来表示:

1
2
3
int myprintf(const char format[], ...)
{
}

然后你要需要一个指向参数的指针:
1
2
va_list ap;             // 定义指针ap
va_start(ap,format); // 初始化ap,将它指向format的末尾,这样它后面就是那个...列表了。

接着就是通过指针ap提取列表里的参数,例如左到右依次是 int、char、double、字符串,那么可以这样:
1
2
3
4
int a=va_arg(ap,int);    // 执行完这个操作之后ap会自动往后跳,比如这里就会跳到这个int的末尾
char b=va_arg(ap,int); // 因为char也是int。。。比较奇怪的设定。。
double c=va_arg(ap,double);
char *d=va_arg(ap,char*);

用完了之后要把ap释放掉:
1
va_end(ap);

字符串处理

  这个瞎处理就好了,一位一位扫format,碰到 % 就后面接一堆判断和 switch,否则就直接输出。
  至于许多小朋友们担心的 \n、\t、\233 等转义字符怎么办,其实 C 已经自动帮你转好的了,它们本身就只是一个字符而已。

输出参数

  有机智的小朋友说直接 _vsnprintf 就好啦。。。快去请卢来佛祖
  基本的思想都是把数字转化成数组,然后倒着输出。这个大家在学进制转换的时候天天玩的了。
  然后为了方便统计一共输出了多少字符,建议最后的输出统一用字符串进行。即把数组也再转换成字符串,丢到统一的输出函数里去。

  麻烦的是实数,你不能把任何一个部分转成整数来处理,因为会爆 long long。然后你还要保持精度。
  所以要想个办法。可以先转成科学记数法,意思就是对于实数 $x$ 要先求出一个最大的 $10$ 的幂 $ep$ 使得 $\frac{x}{ep}\in[1,10)$,然后从高位开始一位一位除下去,存到数组里。当然,视不同的输出格式要做些调整,比如 %f 输出的时候无视 $ep<1$ 的情况。
  另一个办法是 C 里有一个库函数可以直接把实数转成字符串。但是这个精度不好,输出 $2^{63}$ 的时候最后一位就被吞掉了。所以不推荐。
  注意四舍五入,这里会有一个类似于高精度的运算。(恶心吧。。

鬼畜区

  真正实现起来的时候,你会发现 printf 是有多么的鬼畜。。。

  四舍五入对吧,好那我就四舍五入。诶为什么printf("%.f",3.5);输出 3 啊???
  诶为什么printf("%.f",3.55);就是 4 了啊???
  再输出一个printf("%.f",3.5000001);,懂了,这逼居然是恰好 0.5 的时候不进位,要严格大于 0.5 才进位。。。

  好的现在肝完 %f 和 %e 来肝 %g 了。
  诶不对为啥 printf("%.6g",0.00001234);是 1.234e-005 啊??不是说好了默认 6 位精度的吗??诶为啥printf("%.4g",acos(-1));是 3.142 啊这个精度也不对啊??
  输出研究了一通,%f 和 %e 的精度是指小数点后保留多少位,%g 的精度是指有效数字。。。

  好的这些都只是小鬼畜。我们试着输出一发:

1
2
3
int haha=2147483647;
double tst=3.6;
printf("%yha %.yha %33.33ttttt %%..... %%d %hd\n",haha,haha);


  诶这啥玩意???
  再来。。
1
2
double tst=3.6;
printf("%...f %----10.f\n",tst,tst);


  这。。。
  自动去掉重复的东西??
1
printf("%.---3f\n",tst);


  这。。。
  精度自动取绝对值,好像都能解释,没什么问题。。那大概就是这样了吧,验证下。。

1
printf("%----10.....----10f\n",tst);

  ?????????????????

  弃疗

代码

先是 myprintf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<stdarg.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define maxlen 1000005

typedef unsigned int uint;

const double eps=1e-14;

int max(int x,int y) {return (x>y) ?x :y ;}

int charnum,d0,d[maxlen],len,ali,width;
char S[maxlen];
void Put(char ch)
{
putchar(ch);
charnum++;
}

void PutString(char *S,int limit)
{
int len=strlen(S);
if (limit>-1 && limit<len) len=limit;
if (ali) fo(i,1,width-len) Put(' ');
fo(i,0,len-1) Put(S[i]);
if (!ali) fo(i,1,width-len) Put(' ');
}

void PutChar(char ch)
{
S[0]=ch, S[1]='\000';
PutString(S,-1);
}

void PutInt(int x,int limit)
{
if (x<0)
{
S[len++]='-';
x=-x;
}
if (x==0) d[d0=1]=0;
else for(d0=0; x; x/=10) d[++d0]=x%10;

fo(i,1,limit-d0) S[len++]='0';
fd(i,d0,1) S[len++]=d[i]+'0';
S[len++]='\000';
PutString(S,-1);
}

void PutUInt(uint x,int ty,int cap,int limit)
{
if (x==0) d[d0=1]=0;
else for(d0=0; x; x/=ty) d[++d0]=x%ty;

fo(i,1,limit-d0) S[len++]='0';
fd(i,d0,1) S[len++]=(d[i]<10) ?d[i]+'0' :d[i]-10+(cap?'A':'a');
S[len++]='\000';
PutString(S,-1);
}

void DigitToArray(double x,double ep,int len)
{
d[d0=0]=0;
fo(i,1,len)
{
d[++d0]=fmod(x,ep*10)/ep;
x-=d[d0]*ep;
ep/=10;
}
d[d0]+=(fmod(x,ep*10)/ep>5+eps);
for(int i=d0; d[i]>9; i--) d[i]-=10, d[i-1]++;
}

void PutFloat(double x,int acr)
{
if (x<0)
{
S[len++]='-';
x=-x;
}

int w=1;
double ep=1;
for(double xx=x; xx+eps>=10; xx/=10) w++, ep*=10;
DigitToArray(x,ep,w+acr);

int st=(d[0]==0);
fo(i,st,w) S[len++]=d[i]+'0';
if (acr)
{
S[len++]='.';
fo(i,w+1,w+acr) S[len++]=d[i]+'0';
}
S[len++]='\000';
PutString(S,-1);
}

void PutFloatE(double x,int acr,char cap)
{
if (x<0)
{
S[len++]='-';
x=-x;
}

int index=0;
double ep=1;
for(double xx=x; xx+eps>=10; xx/=10) index++, ep*=10;
for(double xx=x; xx>eps && xx+eps<1; xx*=10) index--, ep/=10;
DigitToArray(x,ep,1+acr);
index+=(d[0]>0);

int st=(d[0]==0);
S[len++]=d[st]+'0';
if (acr)
{
S[len++]='.';
fo(i,st+1,st+acr) S[len++]=d[i]+'0';
}
S[len++]=cap;
if (index>=0) S[len++]='+'; else S[len++]='-', index=-index;
for(int i=100; i>=1; i/=10) S[len++]=(index/i)%10+'0';
S[len]='\000';
PutString(S,-1);
}

void PutFloatG(double x,int acr,char cap)
{
if (x<0)
{
S[len++]='-';
x=-x;
}

int index=0;
double ep=1;
for(double xx=x; xx+eps>=10; xx/=10) index++, ep*=10;
for(double xx=x; xx>eps && xx+eps<1; xx*=10) index--, ep/=10;
DigitToArray(x,ep,acr);
index+=(d[0]>0);

int st=(d[0]==0);
if (index<-4 || index>=acr)
{
while (st<d0 && d[d0]==0) d0--;
S[len++]=d[st]+'0';
if (st<d0)
{
S[len++]='.';
fo(i,st+1,d0) S[len++]=d[i]+'0';
}
S[len++]=cap;
if (index>=0) S[len++]='+'; else S[len++]='-', index=-index;
for(int i=100; i>=1; i/=10) S[len++]=(index/i)%10+'0';
} else
{
int w=max(index+1,0);
while (st+w-1<d0 && d[d0]==0) d0--;
if (!w) S[len++]='0';
else fo(i,st,st+w-1) S[len++]=d[i]+'0';
if (st+w-1<d0) S[len++]='.';
fo(i,1,-index-1) S[len++]='0';
fo(i,st+w,d0) S[len++]=d[i]+'0';
}
S[len]='\000';
PutString(S,-1);
}

int myprintf(const char format[],...)
{
int n=strlen(format);
charnum=0;
va_list ap;
va_start(ap,format);

fo(i,0,n-1) if (format[i]=='%' && i<n-1)
{
int now=i;

ali=1, width=0;
int acr=0, hasacr=0, h=0, l=0, get;
while (format[i+1]=='-') ali=0, i++;
while (format[i+1]>='0' && format[i+1]<='9') width=width*10+format[++i]-'0';
if (format[i+1]=='.') hasacr=1, i++;
if (format[i+1]=='-') i++;
while (format[i+1]>='0' && format[i+1]<='9') acr=acr*10+format[++i]-'0';
if (format[i+1]=='h') h=1, i++;
if (format[i+1]=='l') l=1, i++;

len=0;
switch (format[++i]) {
case 'd': case 'i':
get= h ?(short)va_arg(ap,int) :va_arg(ap,int) ;
PutInt(get,acr);
break;
case 'o':
get= h ?(short)va_arg(ap,uint) :va_arg(ap,uint) ;
PutUInt(get,8,0,acr);
break;
case 'x': case 'X':
get= h ?(short)va_arg(ap,uint) :va_arg(ap,uint) ;
PutUInt(get,16,(format[i]=='X'),acr);
break;
case 'u':
get= h ?(short)va_arg(ap,uint) :va_arg(ap,uint) ;
PutUInt(get,10,0,acr);
break;
case 'c':
PutChar(va_arg(ap,int));
break;
case 's':
PutString(va_arg(ap,char*),(hasacr)?acr:-1);
break;
case 'f':
PutFloat(va_arg(ap,double),(hasacr)?acr:6);
break;
case 'e': case 'E':
PutFloatE(va_arg(ap,double),(hasacr)?acr:6,format[i]);
break;
case 'g': case 'G':
PutFloatG(va_arg(ap,double),(hasacr)?max(acr,1):6,format[i]-2);
break;
case 'p':
PutUInt((uint)va_arg(ap,void*),16,0,8);
break;
case '%':
Put('%');
break;
default:
fo(j,now,i) Put(format[j]);
}
} else Put(format[i]);

va_end(ap);
return charnum;
}

然后是一些测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include"myprintf.c"

int main()
{
//freopen("main.out","w",stdout);

long long ll=2147483647; ll++; ll=ll*ll; ll=ll-1+ll;
printf("%e\n",acos(-1)-3);
myprintf("%e\n",acos(-1)-3);
printf("%f %f %f\n",-acos(-1),(double)ll,1.0/22);
myprintf("%f %f %f\n",-acos(-1),(double)ll,1.0/22);
puts("");

int a=20, b=30, n;
double c=10.5, d=100000000;
char e='m';
char s[9]="myprintf";
myprintf("%d %d\n",a,a+b);
myprintf("%f %e\n",c,d);
n=myprintf("%c\t%s\n",e,s);
myprintf("%d\n",n);
puts("");

printf("%g %g %.2g %.2g\n", 0.00001234,0.0001234,123.45,23.45);
myprintf("%g %g %.2g %.2g\n", 0.00001234,0.0001234,123.45,23.45);
float x=654.321; double pi=acos(-1);
printf("%f %e %g %.8f %.8e %.8g %.4g\n",x,x,x,pi,pi,pi,pi);
myprintf("%f %e %g %.8f %.8e %.8g %.4g\n",x,x,x,pi,pi,pi,pi);
printf("%g %.5g %.3g\n",0.034,9999.99,0.0009999);
myprintf("%g %.5g %.3g\n",0.034,9999.99,0.0009999);
puts("");

int haha=2147483647; double tst=3.6;
printf("%yha %.yha %33.33ttttt %%..... %%d %hd\n",haha,haha);
myprintf("%yha %.yha %33.33ttttt %t %..... %%d %hd\n",haha,haha);
printf("%33.33\n");
myprintf("%33.33\n");

printf("%----10.....----10f\n",tst);
myprintf("%----10.....----10f\n",tst);
printf("%-10..3f\n",tst);
myprintf("%-10..3f\n",tst);
printf("%...f %----10.f\n",tst,tst);
myprintf("%...f %----10.f\n",tst,tst);
printf("%.---3f\n",tst);
myprintf("%.---3f\n",tst);
printf("%d\n",printf("\0"));
printf("%d\n",myprintf("\0"));
}