【随笔】从char类型到玛雅历法
今天看书,看到一段代码,下面是其中的一个申明部分,是记录每月天数的表。
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
复制代码
一开始并没在意数组的类型以及数组中的元素类型,后来看到书上特意提了一下。
“这里之所以将daytab的元素声明为char类型,是为了说明在char类型的变量中存放较小的非字符整数也是合法的。”
恩,就是有点小疑问= =。。关于这个存放较小的非字符整数的合法性问题。= =。。各位怎么看。。= =
首先,这个问题跟声明无关。
在C语言中,char类型,是整数类型的一种。
假定在你的系统的某个C实现上面,一个char类型数据占用了1个字节的长度,即8 bits。
我们知道,在一个bit中的,要么是一个0,要么是一个1。
所以,8个bits组成的序列,可以表示2的8次方个不同位向量,即
0000 0000
0000 0001
0000 0010
……
1111 1110
1111 1111
一共256个。
那么,它们可以和256个整数一一对应。
即所有的char类型的数据所构成的集合中的元素,总可以和256个整数一一对应。
这256个整数,可以是[0,255],也可以是[-128,127],也可以是其他的整数集合。
在我的系统上,文件 /usr/include/limits.h 给出了本系统上某个C实现(GCC)的约定:
/* Number of bits in a `char'. */
# define CHAR_BIT 8
/* Minimum and maximum values a `signed char' can hold. */
# define SCHAR_MIN (-128)
# define SCHAR_MAX 127
/* Maximum value an `unsigned char' can hold. (Minimum is 0.) */
# define UCHAR_MAX 255
/* Minimum and maximum values a `char' can hold. */
# ifdef __CHAR_UNSIGNED__
# define CHAR_MIN 0
# define CHAR_MAX UCHAR_MAX
# else
# define CHAR_MIN SCHAR_MIN
# define CHAR_MAX SCHAR_MAX
# endif
注意上面最后一小节。其中的“__CHAR_UNSIGNED__”宏是否被定义,是由GCC根据我的机器(严格地说,是目标机器)的背景情况决定的(用户不可干预),当且仅当我的机器满足“char类型是一种
无符号的整数类型”这个背景条件时,这个宏将被定义。
在我这里,现场的实际情况是:这个宏没有被定义,即:作为GCC的目标机器,具有“char类型是一种
有符号的整数类型”的属性。
此时,我们回到上面那一个小节:因为“__CHAR_UNSIGNED__”宏没有被定义,则GCC将
“CHAR_MIN”宏定义为“SCHAR_MIN”宏,即整数-128。
“CHAR_MAX”宏定义为“SCHAR_MAX”宏,即整数127。
所以,在这个实现场合中,所有char类型的数据之集合中的元素,可以与[-128,127]这个整数集合中的元素一一对应。
或者,干脆直接说:char类型的数据之集合中的元素,是从-128到127这256个整数。
而当我们利用“字符常量”这种机制(特别注意:字符常量不是所谓的“char类型常量”,后者在C语言中是不存在的!)来为char类型的数据对象赋值的时候,一种“字符-整数”的对应关系,就是一个基本前提。
这种对应关系的法则,由不得你来决定,一般情况下,它会遵照绝大多数计算机基础教材的附录
(通常是附录1或附录A,可见其基要性)中的《ASCII字符集》所揭示的字符与整数的对应关系。
比如:
字符'\0'对应且仅对应着整数0(十进制);
字符'\1'对应且仅对应着整数1(十进制);
字符'\2'对应且仅对应着整数2(十进制);
……
字符'\10'对应且仅对应着整数8(十进制) —— 可见,斜杠后面跟着的是一个八进制整数;
字符'\11'对应且仅对应着整数9(十进制);
……
字符'\r'对应且仅对应着整数13(十进制) —— 转义序列“\r”指代一个CR(Carriage Return)字符,即回车。回车字符的ASCII编码是13(十进制);
……
字符'\x10'对应且仅对应着整数16(十进制);—— 可见,斜杠与x后面跟着的是一个八进制整数;
……
字符'A'对应且仅对应着整数65(十进制)—— 字符A的ASCII编码是65(十进制);
字符'B'对应且仅对应着整数66(十进制);
……
字符'a'对应且仅对应着整数97(十进制);
字符'b'对应且仅对应着整数98(十进制);
……
这样的对应关系,实际上,就是将char类型的数据之集合中的[0,127]这个真子集中的元素,与[0,127]这个整数集合中的元素一一对应。
同样要
特别注意的是:作为表达式的'A'
—— 其类型是int,而非char!
即“'A'”这样的字符常量,其数据占用的宽度与一个int类型数据所占用的宽度是一致的,通常是32bits,而不是8bits。
char x;
x = 'A';
这样的赋值运算,蕴含了将一个通常为32bits的数据“截短”为8bits的数据的转换过程(这种情况是合法的)。
回到楼主的题目。。。
人类社会目前所最普遍使用的历法 —— 格列高利历(俗称公历),将一年分为12个月,并按照大体均等的原则规定了每个月的日子数,这个日子数不会超过31。
因为[0,31]
(本题额外多设了一个0)这个整数集合,是[-128,127]或[0,255]的一个真子集;或者,换一种看法:[0,31]这个集合中的任何元素,相对于它的超集中的最大整数即127或255来说,都是“
较小的”,所以,[0,31]这个整数集合中的任何元素,都可以与char类型数据集合中的元素单射地对应。
简言之,0、28、29、30、31这五
样整数,完全可以用char类型来存储、读取和表达。
注意:在代码(数组元素的初始化部分)中出现的这些样式的整数(除了0之外),将被视为关于十进制常量的表达式,它们的类型是int,当然关于常量“0”的那个表达式,其类型也是int,所以,它们均占用了通常是32bits的长度。(这个“整数常量的代码被编译器解释/认可为int类型”的基本原则,请参见键盘农夫(KBTiller)大虾的《狂人C》)
同前述之理,用这样的表达式为char[][]类型的数组之元素初始化,就会蕴含一个将数据“截短”为8bits的数据的转换过程(这种情况是合法的)。
综上所述,由这五
样整数构成的一个二维数组(2x13个元素),可以是static
char [2][13]类型的。
考虑到提设的巧妙 —— [0,31]这个整数集合(含32个整数),以ASCII编码看来,恰巧是“控制字符(Control Character)”区间。
控制字符,也叫“非打印字符(Non-Printing Character)”,它们不仅是“不可见的”,而且是可以用来操控终端设备的(最早是操控TTY即电传打字机的)。
经常接触终端的用户,大多都有这种经验:在由“可见字符”组成的序列中,搀杂控制字符,很可能导致打印(显示)的混乱。
所以,一些用户对控制字符,是敬而远之的。
或许,他们有“将控制字符保存进char类型的对象中会导致数组元素序列混乱”的顾虑。
而作者在这里所说的“在char类型的变量中存放较小的非字符整数也是合法的”,应该就是为了破除这种顾虑。
这里的“非字符”,应该就是指控制字符(作者在这个语境中,将不可见字符当作“非字符”看待)。
而这里的“较小的”一词,应该是对于“非字符整数”本身的描述(即“非字符整数”在整个ASCII字符集当中,是“较小的”),而不是一个限制性定语(指整个“非字符整数”集合中“较小”的那个部分)。
讲到这里 —— 一个颇为微妙的事情,似乎呼之欲出 ……
我们注意到:在ASCII字符集中,控制字符(除了DEL字符)全部位于前32个(即从0到31)。
32这个数字,是2的一个正整数次幂,即2的5次方。
而大多数历法中的一个月的日子,都在30附近 —— 这是因为月相的变化周期(即朔望月)是大约30天左右,所以叫“月(Month/Moon)”。
也就是说:
朔望月的长度,是“一天”长度的约30倍。
“一天”传统上都是基于“视太阳”的“太阳日”,即地球相对于“视太阳”自转一周的时间
(不同于地球“自己”相对于遥远恒星的自转一周的时间),也就是观测地的一个正午到下一个正午(可以藉由日晷测量)的时间。
而我们今天普遍使用的格列高利历(俗称公历),遵守“以太阳回归年为一年”的原则。(太阳回归年:在地球看来,太阳从黄道上的一点再回到该点的周期。)
可以藉由观测得知,一个太阳回归年大约是365.24个“太阳日”,这就使得:我们公历的一年有365日或置闰的366日。
公历将它的“年”与传统的朔望月相调和,就使得:公历需要有12个月,且每个月的日子数在30或31附近。
30或31这个数,和2的5次方即32,非常接近,我并不想武断地下一个“这不是巧合”的结论,但是,这是否能揭示一些群族将二进制/八进制/十六进制/三十二进制作为历法基础的可能性?
不过,这还得和中国传统巫术文化如《易》中所体现的所谓“二进制思想”、“八进制思想”或“六十四进制思想”区别开来。
我的意思是:朔望月相对于太阳日的倍数(大约是30)—— 即太阳-地球-月球三天体的运动关系,是否可以用“2正整数次幂之进制”的计数法来*解释*?
我觉得,这种希望非常渺茫。上述天体的运动关系,倒是揭启了人类对于“12进制”、“60进制”和“360进制”的自觉运用,但却不是二进制/八进制/十六进制/三十二进制。
那我们调转方向,查考一些“非主流”民族的计数制/进位制。
比如,把全人类“折腾”了一番的玛雅民族(和他们的历法)。
玛雅民族(玛雅文明)的计数制/进位制和他们的历法(即对天体运行规律的揭示)是分不开的。
玛雅文明普遍采用18进制和20进制(已经可以注意到:18与20的乘积是360)。
20进制,基于玛雅文明的每个月份的日子数,即他们的一个月有20天。
为什么他们的一个有20天,而不是30天?
因为玛雅人采用双手双脚的指/趾端数总和,即20,来作为他们计数的基本基数。
而他们倚靠对金星的观测,来确定太阳年。这个单位太阳年的长度是365天多一点。
在晚上欧巴桑没有电视可看、屌丝没有网可上、美白富没有夜店可泡、仰望星空时也没有大气污染和光污染干扰的古代玛雅人看来,金星是夜晚天空中亮度仅次于月亮的天体。(有人藉此来佐证“古代玛雅时期,月球尚不存在”,这真是匪夷所思、令人难以置信!)
玛雅文明利用太阳-地球-金星三天体的运动关系,确定了他们的历法。在一个月有20天的情况下,为了调和约365天多一点的太阳年,就要把一年分为18个月或置闰的19个月(第19个月很短,只有5天多一点)—— 这个叫Haab历。
这样,我们就不难理解,玛雅文明采用了18进制和20进制的计数法。
又及,其实,玛雅人还有一种更为首要的历法:即260天(260 = 13 x 20)的年历。
但其中的因子13,目前没有证据表明跟天体运行有关。一些零散的学说,认为260天跟人的妊娠周期(以最后一次期经不至到分娩计日)有关 —— 这个叫Tzolkin历。
那么 …… 采用基于二进制/八进制/十六进制/三十二进制的历法的“民族”,恐怕仅仅存在于计算机世界里了,呵呵 ……
以上,仅供参考,呵呵 —— :)
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法