An image to describe post 你知道33进制吗?

图片摄于瑞士。


“什么?33进制?我只知道2进制,10进制,16进制。你想说的不是《失恋33天》吧!”虽然我预计大家不会这么大反应,但我不能完全杜绝这种可能性。

为什么要谈33进制?在谈这个问题之前,我得先讲点别的。

我读高中的时候,物理老师是特级教师,但我们并不觉得他有多么厉害,反而有些烦人。

最明显的表现是,讲力学题目的时候,不管是什么题目,上来首先就是拿三角板画坐标系,然后按照坐标系来分析受力情况。我们嫌烦,题目做了那么多,到底有什么力一眼就可以看清楚,画坐标系多麻烦呀。但他总是念念有辞:你们要晓得,来来去去只有重力、摩擦力几种,画了坐标系就清白了,如果不画坐标系,你们不晓得会搞出几多千奇百怪的力出来,什么扯力、提力…… 这时候,台下大家往往都在偷笑:谁会犯那种低级错误啊。

等上了大学,一件小事改变了我对物理老师的看法。我去给上高中的朋友辅导功课讲物理题目。让我无奈的是,简单的力学题目他怎么也解不对,总是一头雾水。但每次我给他讲完,他又会说“哦,原来是这样”。来回几次让我有点沮丧了,才发现问题所在:他的老师从来也没有教他们从坐标系开始,课堂上讲解题目似乎都是“天经地义一点就透”的,可是真正自己解起题目来,“天经地义”就成了“天堑”了。但是,他又无论如何不愿意每次解题先画坐标系,因为“太费事”了。

这件小事让我印象深刻,从此理解了“优秀教师”究竟优秀在哪里。同时也认识到,要想解决复杂问题,就得不怕麻烦,要有耐心一开始做些“无用功”。

好,现在可以回到33进制的问题了。我在面试中见到很多程序员,满足于框架和类库玩得熟练,甚至引以自豪,问几个“简单基础”的问题就无法回答,学校里学的理论全忘光了还觉得无所谓,甚至觉得问这些问题是有意刁难。

不可否认,这样的程序员确实在很多行业取得了成功,创造了价值,但是同样不可否认的是,他们也制造了很多问题。而这些问题,本来只要对基础知识和理论体系有多一点点的了解,就不应当存在的。

举个例子吧。大多数计算机科学教材都是从2进制开始,然后介绍8进制、16进制。很多人都觉得这些东西没什么用——计算机都会处理的,花时间学这些纯粹是浪费。

但是,真的只是浪费吗?

我在不只一家公司遇到过这样的需求:需要有个程序来生成流水号。所谓流水号,也叫序列号、SN号。通常,它们是大量分配,分配到每一件产品上,以便跟踪和追溯。

流水号的生成看起来简单,需求也确实简单:

  1. 流水号必须是单调增加的,也就是说,小的流水号一定先分配,大的流水号一定后分配;

  2. 流水号是可以计算的,如果知道最小流水号,再分配一千个流水号,必须能很快能算出最大的那个流水号;

  3. 流水号要能方便计算“空隙”,如果发现同样的两个产品出问题,很可能说明期间发售的产品都有问题,通过前后两个流水号就可以计算出受影响的范围;

  4. 为了方便识读、避免混淆,流水号一般要去掉一些字符,比如保留数字1就要去掉字母I和L(因为流水号在沟通传递中不能严格控制大小写),保留数字0就要去掉字母O;

通常来说,就这几点需求。但是我见过的程序员里,真正能把流水号分配程序写到没有问题的少之又少。如果你也觉得挺简单,我来介绍大部分程序员的思路,看看你是否也这样设计:

  • 把所有能用的字母都挑出来,从小到大排列好,生成一个数组;

  • 然后写一个函数,输入参数是要生成的流水号的数目,输出是一个字符串数组,函数中用for循环遍历数组,每生成一个就记录到结果数组里,如果在这一位遍历完了,就按照自己设定的进位规则进一位,然后在这一位重新开始循环;

  • 还需要写个比较函数,如果输入两个流水号,先把它们拆成单个字符,然后逐个比较,判断大小关系;

  • 如果要计算两个流水号之间的范围大小,先找到比较小的流水号,再调用生成函数不断迭代计数,直到生成较大的流水号为止;

粗看起来,这种设计思路似乎没有问题,也符合逻辑。不幸的是,我见过的程序员里,似乎没有人能把这段程序写到不出问题的,结果线上程序的死循环也。注意,我说的是工业级别的不出问题。这也难怪,以前有调查说,90%的计算机科学家无法在2小时内写出完全正确的快速排序程序。我相信这个数据,因为很多人都知道,道理很容易掌握,但细节和边界条件太难考虑了。

那么,这个问题真的有什么“好办法”解决吗?其实,真的有。

计算机理论的书里,凡是讲到2进制,都会介绍2进制和10进制之间的换算,两者是可以完全换算的。但没有2进制,就没有今天的计算机,其中的道理有多少人真正想过呢?如果你认真想过,就会明白为什么还有2进制、8进制、16进制,它们适合解决什么问题。

如果你仔细阅读编程语言的文档就会发现,在很多语言里进制转换不是只提供了2进制、8进制、16进制、10进制这几种选择,而是可以让你随便输入一个整数表示进制呢?比如在Java里我们都知道这样:

//16进制转成10进制,得到255

Integer.valueOf("FF",16).toString();

//10进制转成16进制,得到FF

Integer.toString(255, 16);

它调用的是Integer提供的方法Integer.valueOf(string s, int radix)和Integer.toString(int value, int radix)。既然表示进制的参数radix是一个整数,那么是不是能“创造”其它进制呢?比如9进制?显然可以。

//16进制转成9进制,得到2810

System.out.println(Integer.valueOf("3762", 9).toString());

所以,33进制也是可以的。只是这时候数字不够用,按照官方文档,会“征用”更多字符,也就是小写字母a-z,这其实是16进制表示法的延续:

The remaining characters of the result represent the magnitude of the first argument. If the magnitude is zero, it is represented by a single zero character '0' ('\u0030'); otherwise, the first character of the representation of the magnitude will not be the zero character. The following ASCII characters are used as digits:

0123456789abcdefghijklmnopqrstuvwxyz

来写程序看看

//10进制转成33进制,得到9

System.out.println(Integer.toString(9,13));

//10进制转成33进制,得到a

System.out.println(Integer.toString(10,13));

//10进制转成33进制,得到b

System.out.println(Integer.toString(11,13));

//10进制转成33进制,得到c

System.out.println(Integer.toString(12,13));

//10进制转成33进制,得到10

System.out.println(Integer.toString(13,13));

再来看流水号的问题,按照关注点分离的原则,我们会发现流水号系统主要在两个层面:

  • 在底下,它其实是数值系统,所以可以进行加、减、比较大小等操作,这和我们熟悉的十进制系统是一样的;

  • 在表现层,识读的时候它需要表现为某种字符串;

所以我们真正需要的是33进制的系统:数字0-9,字母A-Z,去掉字母I, L, O。所有的运算、分配、比较,都是基于这个33进制的体系。Java语言已经提供了现成的33进制,只是它用到字符0-9, A-W(去掉X, Y, Z)。这不符合外显的要求,所以只需要一个映射函数进行字符转换。

An image to describe post 你知道33进制吗?

于是一切问题都迎刃而解了:

  • 要比较两个流水号的大小,查询映射表把它们转换为系统支持的33进制再转换为10进制Integer,就可以直接比较;

  • 要分配若干流水号,比如1000个,可以先把起始流水号转换为10进制数字,然后加上999则得到末尾流水号,对其中的每个10进制数,先转换为33进制,再查询映射表得到外显形式;

  • 在流水号的传输、计算时,再也不要用巨大的字符数组来全部存储,只需要使用首尾两个10进制数字就可以表示;

  • 要计算两个流水号之间的空隙就更简单了,都换成10进制数再做减法;

好玩的是,每次我给程序员讲清楚这样的设计,答案都是“哦,原来是这样”。我本来想顺水推舟,建议大家认真去读读基本的理论和算法,答案也往往还是“哦”……

如果说第一个“哦”可以让人体会到恍然大悟的愉悦,第二个“哦”往往就是让人心灰意冷的敷衍了……

如果你已经看到这里,我衷心希望,你不是会习惯性说出第二个“哦”的人。


“天天用英语”是由李笑来老师总监制的实用英语日播栏目,每天从英美主流媒体选取文章,陪伴大伙“用”一小时英语。推出至今,已经聚集了超过1万名英语学习者。

“天天用英语”的出品方艾德睿智(EDUISE)成立于2006年,由长期从事英语教育培训的李笑来先生和熊莹女士(曾任新东方国外部主任,新航道集团学习中心事业部总监)共同创办。

目前“天天用英语”的用户和业务增长迅猛,急需靠谱的Node.JS开发人员,地点北京。有兴趣的朋友请扫描下面的二维码,体面的薪资待遇和舒适的工作环境在等你。

An image to describe post 你知道33进制吗?