相信 HNOI2017 Day2 T2 的 Special Judge 被众选手 hack 的事件已经广为人知. 今天要重测, 发现 Special Judge 做了一个小小的改动, 于是来做一个小小的研究. 本省有一位选手就这样获得了100分, 但是被众人举报了......其实我和同学都挺佩服他的~ uoj群里有人说: 成功hack special judge, 奖励100分. 有道理...... nan 是 not a number 的缩写, 表示一些特殊的浮点数值.
0.0/0.0
, sqrt(-1)
不会报错, 没有 warning, 不会抛出异常, 不会直接导致运行时错误, 而是会返回 -nan.
注意, nan 属于浮点类型. 计算0/0
, 如果分母的值是常量, g++会提示: warning: division by zero [-Wdiv-by-zero]
, 程序不能正常运行 (至少Linux下如此): 浮点数例外 (核心已转储)
.
scanf
可以读入nan
, -nan
, inf
, -inf
. printf
可以输出它们.
nan 之间的比较运算奥妙重重:
nan == nan false
nan != nan true
nan > nan false
nan < nan false
nan >= nan false
nan <= nan false
并且, 它不等于, 大于, 小于任何浮点数.
nan == 1 false
nan != 1 true
nan > 1 false
nan < 1 false
nan >= 1 false
nan <= 1 false
特别地, 不会小于inf
:
nan < inf false
涉及到 nan 的四则运算返回 nan 或 -nan.
nan + nan nan
-nan -nan
绝对值 (cmath
里的fabs
) 运算:
fabs(nan) nan
fabs(-nan) nan
强制类型转换 (64位):
(int)nan -2147483648
(long long)nan -9223372036854775808
(unsigned long long)nan 9223372036854775808
(long double)nan nan
整型->nan的逆转换是不行的.
百度百科说: nan 表示为指数域全为1, 尾数域不等于0的浮点数. IEEE 标准没有要求具体的尾数域, 所以 nan 实际上不是一个, 而是一族.
再来看看考虑不周全的 Special Judge 的写法. 以 HNOI2017 Day2 T2 为例. 以下是新下发的 Special Judge:
double a, o;
while (fscanf(ans, "%lf", &a) != EOF) {
if (fscanf(out, "%lf", &o) != 1) result(0, "Contestant output is shorter than answer");
//if (_isnan(o)) result(0, "nan: Not a Number");
//if (fabs(a - o) > eps * a) result(0, "Difference: %.10lf, wrong.", fabs(a - o) / a);
if (!(fabs(a - o) <= eps * a)) result(0, "Difference: %.10lf, wrong.", fabs(a - o) / a);
}
可以推测, 以前是这样的:
double a, o;
while (fscanf(ans, "%lf", &a) != EOF) {
if (fscanf(out, "%lf", &o) != 1) result(0, "Contestant output is shorter than answer");
if (fabs(a - o) > eps * a) result(0, "Difference: %.10lf, wrong.", fabs(a - o) / a);
}
很不幸, nan > eps * a
永远为假, 所以本题输出nan
就可以AC了.
而修改为!(fabs(a - o) <= eps * a)
, nan <= eps * a
为假, !(nan <= eps * a)
为真, 正确地区分出nan
.