微信号:yurii-says

介绍:我是这么以为的,当然你也可以那么以为

你知道33进制吗?

2017-05-19 08:39 余晟

图片摄于瑞士。


“什么?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)。这不符合外显的要求,所以只需要一个映射函数进行字符转换。

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

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

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

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

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

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

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

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


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

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

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


 
余晟以为 更多文章 “学习讲究症”不可取 活着也可以不为了改变世界 谈谈纪录片 为什么年轻人越来越「穷」? 关于监控的几个基础问题
猜您喜欢 Linux网络子系统安全性模块详细分析之文件nf_nat_core.c 安装好APT检测设备,高枕无忧了? DevOps读书清单:十本应该放入书架的经典 微信PaxosStore:深入浅出Paxos算法协议 "WEAK, STRONG, UNOWNED, 老天爷!" - Swift中的引用关系说明