进制换算——从二到三十六,每一位都写出来

把一个整数或一个小数在 2 到 36 进制之间换算,并且把算的过程读出来:逐位的账,这个数在哪些进制里写尽、在哪些里永远循环,8、16、32、64 位两种端序的字节,还有底下拿你自己的浏览器的 Number、parseInt、BigInt 和 DataView 跑的一遍对账——每一处差多少都按数字印出来。你打的任何东西都不离开这台设备。

进制、长除与对账表

进制是写法,不是值

进制是写一个值的方法,不是值本身。同一串 1011,按二是十一,按十是一千零十一;桌上那堆东西没有变, 变的只是把它记下来的方式。所以分两步走,把这两步分开就是整个设计:

  1. 把你的数字读成一个确切的数。 一个数由小数点前的一串数字和点后的一串数字组成。前面那串按 第一张表里的规则变成整数——当前的总和乘以进制,再加上这一位。后面那串变成若干份中的一个计数, 每份都被进制除了那么多次,次数就是它后面的位数。两者相加,约掉公因子,就得到一个整数在另一个 整数之上。这一对才是那个数。它是完整地拿着的,外面没有定宽的盒子,所以长数字里的第二十一位和 第一位一样真。
  2. 把这个对再用长除法写出来。 按五写:除,留下余数;余数乘五,再除。余数归零时展开就结束。 某个余数第二次出现时,两次之间的那些位就永远重复,长除把这一块放进方括号。表里每一个进制 都是从同一个对上做出来的,所以这些栏当真是同一个数的不同写法——这正是这张表可以拿去核对的那句话。

算术到此为止。打印之前没有任何东西被缩放、舍入或塞进六十四位里。

四张表

逐位——你的数一个一个字符来:这个字符、转换器给它的值、到此为止的总和。这是学校里最长的那道 除法公开做了一遍,也是另一个文字的数字、逗号、下划线、混进来的字母被点名而不是被悄悄忽略的地方。

每一个进制——同一个数按二到三十六写出来,每个说清四件事:那些位、它是否结束、是否循环、以及 你自己的浏览器的 Number 对同一个值打印什么。分组选择器给你四种:常见的四个、全部三十五个、只列 这个数能除尽的、只列它循环的。三十五行长数字串一次看太多,所以默认是四个。

字节——你的数的整数部分按 8、16、32、64 位铺开,大端小端各一份,按无符号读,(你要求的话)也按 有符号读。每一行把转换器自己的字节和你浏览器的 DataView 并排放。装不下的数在这里没有被拒绝:转换器 打印它的低位,因为那正是一台机器把数塞进那么小的盒子时留下东西的方式,最后一栏就是这么写的。

与你的浏览器对账——二十来行,把转换器的答案和引擎的答案并排。这是一次对账,不是给你这台机器打分: 每一行都是同一个问题问两个实现,判定栏说的是哪些答案对上了。

一个分数在哪些进制里能除尽,在哪些里不能

一个分数在某个进制里能除尽,当且仅它约简后的分母能被这个进制的幂约掉;不能就永远循环。五分之一—— 已约简,分母是五——在按五、十、十五、二十、二十五、三十、三十五写的进制里能除尽,从二到三十六里没有 别的。三分之一在按三写的进制里除尽,别的都不行。十分之一在十、二十、三十里除尽。十六分之十没什么好看 的:约简后是八分之五,而八是二的幂,所以它在按二写的进制里除尽——因为"除尽"说的是这个数,不是你 恰好怎么把它写出来。

页首那行汇总对你输入的数把这件事直说了,因为它是一个分数唯一有干净答案的问题。也正是它让十进制小数 和二进制的机器这么常地谈不拢:一个 double 是一个整数在二的某次幂之上,所以十分之一、十二分之一、 九分之四它只能近似的拿着。这不是浏览器的毛病,也不是转换器的短处。它就是"三分之一在十进制里没有最后一位" 这件事,搬到一个以二为基底的数系里。

为什么最后一列是一个数,不是一句警告

把 18446744073709551615.2 交给 Number,它答 18446744073709552000。多数页面到这里会印一句提醒。 算术去读你浏览器为那个答案留下的六十四位,把它们按 double 的真相解出来——一个整数乘以二的某次幂—— 然后相减。差算出来是 0.8,就印在那一行旁边。进制表和差分表里每一个差异都照此办理:浏览器那串位背后 的确切比例是算出来的,两个数之间的距离以十进制的一个数给出。

三条门槛值得记住,而且每一条都有一行是把它演出来而不是说出来:

  • 九千零七十一万亿……——9007199254740991,一个 double 能独占着记下的最大整数,再大就有邻居来 抢同一个位置。过了它,整数开始被归到最近的那个装得下的偶数上,那一行会写出归走了多少。
  • 指数。 Number.prototype.toString 带进制参数时永远不用 e,但光 toString 在大约 1e21 处就 换成指数写法。所以一行可能只在拼法上不一致,数本身是一致的。这样的行会这么说,并且先核对指数 里面那个数才这么说。
  • 七分之一按三写。 确切的解永远循环;你浏览器的答案在三十七位后停了,因为 double 总会停。这两个 是不同的数,那一行把距离印出来。如果你只想知道两个引擎在哪里分手,这是最干净的一眼。

拒绝把一种意思读成另一种

规则是:你按一种意思写的数,不能被按另一种意思读走。所以浏览器引擎会猜的地方都还给你:

  • 别的文字里的数字。 阿拉伯-印度数字 ١٢٣、上标二、半的商用分数记号、数学粗体数字,全都拒绝。 这是量出来的,不是假设的:Number 对每一个都答非数,所以转换器在这一点上不比跟它对账的引擎弱。
  • 空着的输入框。 Number("") 是 0。表单里留空的框是一个没人写过的数,所以转换器请你写一个。
  • 两个进制同时出现。 1e3 对 Number 是一千,对 parseInt 按十六是四百八十三,因为按十六 e 是一个数字。转换器要么把 e 当数字读,要么根本不读,拒绝猜你说的是哪一个。
  • 前缀报的进制和框子设的不一样。 设在十六的框子里写 0b101:前缀报的是它自己的进制,转换器不在 两者之间替你决定。这里两个引擎也分开了——Number 把 0b101 读成五,parseInt 按二读那个零, 在 b 处停下,答零。
  • 下划线、逗号、中间的空格、第二个小数点、单独一个正负号。 1_0 是给源代码用的分隔符,不是给 程序读的文本,BigInt 和 Number 都直接拒绝它;parseInt 靠停下来答 1。转换器也拒绝。
  • 二到三十六之外的进制。 这里用的字母表里有三十六个单字符号,所以一个进制需要为小于它的每个值 准备一个数字。一比不是进位记数,而 toString 对这两个都抛 RangeError——所以进制在两个引擎跑起来 之前就被拒了。
  • 六十四进制。 六十四是写字节的一种方式,不是写一个值的一种方式;那一页在 Base64 文本。
  • 把小数变成字节。 一个宽度装的是整数。整数部分进字节表,小数点后面那部分进进制表,转换器说清 哪部分是哪个,而不是悄悄挑一个。

拒绝是被数下来的,不是丢掉:第三张表列出每一件被停下的东西、它改说什么、以及它出现了几次。

上限,以及为什么是这个数

一个数里一千二百位。 对两个任意精度整数做的长除法,耗时随位数的平方增长,而一千二百位以上的数 是一个要打开的文件,不是一个要读的框。

展开一个小数时点后九十个数字。 分母是 d 的分数,循环节最长是 d-1 位,所以九十个数字足以断定 “它能除尽”,也足以让人有信心地说"它在循环"。搜索停下来的地方,格子里就写它停在哪里,而不是宣称一个 它没找到的循环节。

任何一张表三百行,逐位表二十四行。剩下的算术是一样的;表宁可停下,也不把屏幕占满。

只认 ASCII 的数字和字母,这是对差分另一侧那些引擎的一个事实,不是野心不够:一个数字如果连拿来 对账的浏览器都不认,转换器宁可拒绝它,也不收下它。

没有 Unicode 名字表,浏览器侧没有任意精度,也没有浮点捷径。 不带数据文件,不调库。主张是:这里打印的每一样东西,要么是你照着表能用手重复一遍的算术,要么是你自己的浏览器的答案被原样引回来。

微信
微信
支付宝
支付宝