nan

相信 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.