16进制转char_谈谈进制换算

本文介绍了进制转换的重要性,特别是16进制到二进制的转换。通过累积法和打表法,详细讲解了人类如何进行计算,同时也提到了计算机的处理方式。文章适合没有二进制基础知识的读者,讨论了人类计算技巧,包括计算进制数值的便捷方法,以及程序员需要记住的进制转换表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

15f464a483129010427c2bf129af2f00.png
世界上有 10 种人,一种是懂二进制的,另一种是不懂的 ——段子

大学里像咱们要学到这个(虽然我觉得学了也没意义吧),和基础数学数论、基本算法结构有关系,既然这样,不如敞开讲下我之前的见闻。

这篇文章是写给没有太多(二)进制相关知识的人的,不要弄个懂电子的人来看,害怕……

进制换算可以说只是人类里存在的问题,记得蹩脚的罗马数字(就是 II IV 这些加减乘除都用到的大混乱数值[1])吗?它是影响到你的处理能力的。

进制只是一种表示法嘛,数值说实在的还是 加减乘除余(+-*/%)、大小相等性(><, ==)判断,像二进制位运算 (a bitwiseOr b) 就等于抽象的 (a+b) ,机器里一般是补码,这个没学过不讲[2]

下文k表示数位编号(以0始从右数)、a*b表示

equation?tex=a%5Ctimes+b
n**k 表示
equation?tex=n%5Ek 乘方运算。

为啥有这个问题

进制就是位置记数法,因为它的简单性(比如长的数肯定大、进位借位方便)被认作唯一的数值形式[3]
在人类阅读习惯和大端(big endian)字节序里,前面即左端的数位占更大权重,它的“1”会表示更大的值。

与这个相关的数学符号是

equation?tex=%5Cmod%7B%7D
equation?tex=n%5Ek ,以及一个“基数” base (又叫 radix) 值,二进制的基数是 2,每位不大 2 (和
equation?tex=n%5Cmod%7B2%7D 的结果一致)

首先说计算机怎么算,毕竟它的方法死板但接近“表示问题”的本质。

读取到抽象数值(『值』和其表示法没有关系,只提供加乘法、比较的这些操作):

int atoi(char* s) {
  int accum = 0;
  for (char c=*s; s!='00'; s++) { accum = accum*10+(c-'0'); }
  return accum;
}

然后是输出:

#include <math.h>
char* itoa(int n) {
  int size = (int)ceil(log10()); // 算十进制位数,要预先在系统堆分配定长度字符数组,可能不准确。
  char* out = malloc(sizeof(char) * (size+1/*'00'*/)); // 分配 (size+1) 大小的字符(char) 数组
  int i = size-1; // 字符串输出数位号,先 mod(%) 下来的是低位所以从右数。
  while (n != 0) { out[i] = '0'+(n & 10); n /= 10; i -= 1; } // 重复若n不是0,右输出(n取余以10);n=n除10。
}

上面的代码是给相关专业的人看的(也用到了重赋值子程序参数的不良写法 入"C"随俗),也不太好给所有人解释,从0编号的数组的末引数是 (size -1) 以及C字符串是以 '00' 结尾的char数组/内存片指针 这样的细节。

而且上面俩都不处理负数,知道数学弄啥有『定义域』『验证变形/组合有效性』吗?它解决的就是程序的运行时错误。itoa程序在 n<0的时候会陷入无限循环,从0前递减永远到不了0[4]

毕竟咱也不是弄算法可视化的,输出循环变量变动表什么的就免了。(要实例干货的直接翻下一节)

equation?tex=%5CSigma_i%5E%7Blog_%7B10%7Dn%7D+%28%5Cfrac%7Bn%7D%7Bi%2A10%5E%7Bi%2B1%7D%7D%5Cmod%7B10%7D%29%2A10%5Ei

公式不好看吧(也不太严谨,准确说是吓唬你们的)?(0~n的位长度)去求和,【i】量 位值=基数乘方i;(n/位值 取余以 基数)乘位值。其中,常数 基数=10。这样的意思。

好吧,其实真的公式是这个:

equation?tex=%5CSigma_%7Bk%3D0%7D%5E%7B%5Clog_I+n%7D+%5Cllcorner%5Cfrac%7Bn%7D%7BI%5E%7Bk%2B1%7D%7D%5Clrcorner%5Cmod+I%5Cquad%7B%28I%3D2%29%7D

啊不对,其实这也是吓唬人的。

equation?tex=%5CSigma_%7Bk%3D1%7D%5E%7Blog_2+n%7D+n%5Cmod%7B2%5Ek%7D 可以求和每位如
128=1+2+8。最讨厌数学公式了……

但是你仍然不相信它能用?那就比如

equation?tex=%2812%2F10%5E1%29+%3D+1
equation?tex=%2812%5Cmod%7B10%5E1%7D%29%3D2 ,然后你会发现乘
equation?tex=10%5Ei%2C+i%3D1 后它就“算回来了”,第二次取余(mod)根本没意义,总是小于10…… 总之就是这个意思,但愿你能
先明白它编码数值的方式

这时候你就会发现

equation?tex=n%5Ek
equation?tex=%5Cforall%7Bn%7D.n%5E0%3D1
equation?tex=n%5E%7B-k%7D%3D%5Coverbrace%7B%5Cfrac%7B1%7D%7Bn%7D%5Ctimes%5Ccdots%5Ctimes%5Cfrac%7B1%7D%7Bn%7D%7D%5Ek 这些分明就是给位置表示法量身定制的,尤其是小数点后的负次幂。(这段看不懂跳过。)

除了 2、8、16 (+A~F) ,任何进制都可能有,比如说 base64 编码算法实际上就是用 基数=64,以 4 个字节表示 3 字节的内容。

此外,非2倍数的进制也是可以与2、8、16进制互化的,位置计数法只是一种数制、表示法而已,关键在于其实现的四则和大小比较(属偏序关系)操作,除了『进制』,罗马数字也是数值的实现。

那么人类怎么算

对于人类就必须使用各种“计算技巧”了,比如说预先打好结果表如 2**4=16; 2**8=256 什么的,具体怎么算?

因为是从进制数到进制数(都是位置记数法,你也不会 itoa(atoi(n)) 这样麻烦),

假设基数

equation?tex=%28a%3Eb%29 ,则 从b到a (如二到十)和 从a到b 进制
是不同方法。总的来说前者是位合并(稍后有二与十六进制的快速换算法因为
equation?tex=2%5E4%3D16 或者说它俩按4:1的位置块直接对应
[5]),后者是位溢出。

如果目标是十进制,因为咱用的对照表是至十进制的,且会用十进制手算,只用求和每位;但是不管是合并还是溢出,这里都有两种方法——累积法、打表法。累积法一般是计算机使用,打表(即列表)就是靠求和/最大

equation?tex=2%5Ek 匹配再减,递推法从左往右输出。

精力关系,只给 2和10、2和16 的例子了。 16和10 之类的依此类推,把四则操作数里2换成其基数。

2到10(读取)

例如, 101010、11111010、1000001000、1101001111101010

累积法:

原理类似 ((((((((((1*2)+0)*2)+1)*2)+0)*2)+1)*2)+0)==42

注意下最开始有、最后没 的 *2以及中间的 +n,你会发现数位们是按左右顺序出现的,别嫌麻烦!心算下试试,这个只用你记住一个数字。

事 进制2读(:文) 为
  事 读位(:字) = 若此字是'1',1。否则,0。
  变数 n = 读位(此文[0])
  对 此文[1~数略] 里的,
    n = n*2+读位(它)。
  回n

看到 n=n*2+(d*2**k) (其中d数位2**k位置权重)了吗?最后一个数是加上的,累加数可以从数位下标[0]开始,也可以直接以0起始而把[0]累加,试试吧。

比如 111010,记住1*2 ,+1乘2 ,+1乘2,+0乘2,+1乘2,+0。 得 58 。(第2至4步各得6、14、28)

打表法:

下面是

equation?tex=2%5Ek 于 0~9 的值(知乎的表格不可能导入数据,浪费了我好久找插件...):
k2**k
01
12
24
38
416
532
664
7128
8256
9512

equation?tex=11111010_%7B%282%29%7D%3D%5Cbegin%7Bbmatrix%7D+1%261%261%261%261%260%261%260+%5C%5C+2%5E7%262%5E6%262%5E5%262%5E4%262%5E3%262%5E2%262%5E1%262%5E0+%5Cend%7Bbmatrix%7D

equation?tex=%28%5CSigma_%7Bi%3D3%7D%5E%7B7%7D+2%5Ei%29%2B2%5E1+%3D+%28128%5Ctimes+2%29-8%2B2%5E1%3D250

只需要求和有1的位。至于 (3~7)求和以「2乘方它」这个是基于看表(8+8)=16差8优化的,属于比较人性化的巧算,计算机就不需要这种。

说起来,比较 old-school 的教材绝对会列成

equation?tex=%281%5Ctimes+2%5E2%29%2B%280%5Ctimes+2%5E1%29%2B%281%5Ctimes+2%5E0%29+%3D+4%2B0%2B1 多项式的形式再算,你都要用列对照、查表法了还装算盘,没有必要。

当然作为程序员还有些最好要记住的:

k2**k
101024
16 -1 == sizeof(short)32768
16 == sizeof(unsigned short)65536
8 -1 == sizeof(char)128
8 == sizeof(unsigned char/*aka byte*/)256

这个表我们程序员是不会忘,毕竟是 MIN_VALUE这些值域常量的值,偶尔见的到。

10到2(写出)

累积法:

现在的累积法变成取余再换除基数了,就是那个

equation?tex=m%5Cllcorner%5EN 式的“递等式”,噢不对啊它不是等式的……

这个方法对人类而言基本不可用,因为取余数意味着除法,挺麻烦的;理解为:取余基数=拿最右位、换除基数=右移所有数位,如

equation?tex=123%2F10%5E1%3D12 ,即可
[6]

这个过程重复到数为0,或者说<1为止。

打表法:

基本等于累积法,但是从

equation?tex=2%5Ek%5Csim+k 表上找尽可能大的数,叠上二进制位
equation?tex=k ,以
equation?tex=2%5Ek 换减此数,直至此数为0即可。是从左开始拿二进制位的,所以也没有反转结果顺序的麻烦,「最大不大」查表思路
解决的是取余的麻烦

用比较数学的语言讲就是把数拆成

equation?tex=2%5Ek%5Ccdot+n 相加的多项式,降幂排列,至0次,然后按单项有无写1、0。

比如 5250 ,我们知道

equation?tex=2%5E%7B10%7D%3D1024 ,大不了
equation?tex=N%5E%7Bk%2B1%7D%3DN%5EkN 几次
equation?tex=2%5E%7B12%7D%3D1024%5Ctimes+2%5Ctimes+2%3D4096 ,先记住12位是1。

就是 1000000000000记得最右是

equation?tex=2%5E0 所以其实有 13 位

递推处理 5250-4096 = 1154 ,记

equation?tex=2%5E%7B10%7D%3D1024

递推处理 1154-1024 = 130 ,记

equation?tex=2%5E7%3D128

递推处理 130-128 = 2 ,记

equation?tex=2%5E1%3D2

二进制位 12,10,7,1 合并,得 1010010000010

再次强调,最右位是

equation?tex=2%5E0
,而且你编程的时候数组索引也都从0起始,数学这么做是方便位置计数法,计算机科学这么做是为了方便内存对象随机(随便)访问的编址取址。

晓得啥子叫『递推法』不?就是可以『递归』但本质只是循环(条件重复执行)的东西,比如懂 JavaScript 的应该写得出等价的程序版本:

var table = {}; // 2^k~k 的关系表
for (var k=0; k<12; k++) { table[2**k] = k; } // k 规范为指代数位
function decimalToBase2(acc, n) {
  if (n == 0) console.log(acc);// 指定递推基线为 若n是0, IO: 输出结果
  else { // 不然的话
    var _2powK = maxLTE(n, table);
    var k = table[_2powK]; // 找到最大不大的二进制位编号
    return decimalToBase2(acc+2**k/*这里直接累回原数*/,  n-_2powK); // 递推累积剩下的
    //^ 示例只累积回原n, Ctrl+Shift+K 在浏览器控制台里运行并试试支持输出二进制 String !
  }
}
function maxLTE(n, tab) { // 一个函数,子程序。 复用、抽提的思想很重要
  var max = -Infinity; // JS 里是有不大于任何数的最小(浮点)值的
  for (var k in tab) { k=Number(k); if (k > max && k <= n) max = k; }
  return max; //^ 线性查找算法,如果是有序表就不必完全遍历
}

// function=函数/子程序 ; var=其局部变量 ; if-else=若-否则 ; for=对于... ; return=以结果值退出函数
// if (condition) op1(); else op2(); 其中 opX(); 亦可是 {} 顺序执行块,语句。
// == 是相等性、 && 是逻辑且
// for (init; has_next; update)

decimalToBase2(0, 666)==666,这是个习题,有算法甚至程序分析变换基础的同学们可以试试看,以这个刚刚咱手写的算法给它补完实现出来(递推程序,可以改两处)。Ctrl+Shift+K 打开『审查元素』有何不可。(顺便,数学短除法实质上就是递推,如果说每个半框里的数叫n 的话,它和上程序的结构很像)


不少人觉得编程需要数学,甚至生硬地引入各种莫名其妙、毫无实际价值的教程来『证明』这一点。但我想说:比起数学者,编程的人更像周密的活动策划者,程序(及其子部分)可是要针对不同输入在不同环境、不同场景多次执行,并被维护修改很多次的!

编程第一重视程序和数据的结构,优美的程序会直观反映它所处理数据的结构。然后才是关系、逻辑,所以编程和数学的语言文化可以说还不如编程和英语的文化“血缘”近。一个拥有简洁、重视定义力的语言的演说家,已经比许多只知一门语言、看不到复用性的数学家拥有更好的“程序设计”潜力。

最后16进到2的

我就说一个: 0xFA(再懒得用数学表示法了,下标得真是毫无章法[7]),

先无伤大雅的左补满4倍数位长,为 0x00FA[8]

F=15

equation?tex=2%5Ek+ 表示是
equation?tex=2%5E4-1
0b1111

A=10用上表示是

equation?tex=2%5E3%2B2
equation?tex=2%5E3%2B2%5E1
0b1010

因此 0xFA==0b11111010==250

记不住 0~F 即 0~15 对应二进制是几的,0b1111 = (2**3)+(2**2)+(2**1)+(2**0) = 8+4+2+1 = 8+(8-1) = 8+4+(4-1) = 15

这个只是 1:4 映射拼接,比 2、10进制 的逐位累加求和简单多了,一般不可能不利用这个特质。

另外,小数部分转其它进制(写出)可以用打表法(下方有表),或者标准的短除法,比如 1.25 转 base=2:

(整数结果)1.

equation?tex=Row%28n%29%3D%5Cbegin%7Bcases%7D+n%3D2+%26+%28%29%5C%5C+%5Ctextbf%7Belse%7D+%26+%28n%5Cmod+2+%5Cmid+n%29+%5Ccdots+Row%28n%5Cdiv+2%29%5C%5C+%5Cend%7Bcases%7D%5C%5C
equation?tex=1%5Cmid+25%5Ccdots+2%5E%7B-5%7D%5C%5C+0%5Cmid+12%5Ccdots+2%5E%7B-4%7D%5C%5C+0%5Cmid+6%5Ccdots+2%5E%7B-3%7D%5C%5C+1%5Cmid+3%5Ccdots+2%5E%7B-2%7D%5C%5C+0%5Cmid+2%5Ccdots+2%5E%7B-1%7D%5C%5C

(反向,自下而上的阅读顺序,第一位往上位权依次 0.5 (1/2)、0.25 (1/4)、0.125、……)

此算法不对嗯…… 参照 CodeSheep程序羊:都工作两年了,还不知道浮点数如何转二进制? 的方法应该是乘二取整法。

二进制 0.1 表示 0.5 ,和整数部分相反,忽略右部0后,小数越长反而越小。

最后说是因为几乎所有编程语言都不支持非十进小数(无论字面量还是标准库 API),小数部分的读取尾注有,是 读取章—累积法 的反向形式(才发现这个是写出的…… 那读取只能是同整数部分了)。

参考

  1. ^不了解的:罗马数字 I~III 是 1~3 ,V=5、X=10, IV 是 (5-1);VI 是 (5+1) ,依此类推
  2. ^嘴上说没学过,我还是知道负数补码是其正形式二进制取反+1 的。 取反是为了原相加=相减,+1 是为了避免无效的 -0 ,而其实 N位无符号数的最大值 (2**N-1) 减去的就是"0"占的位置
  3. ^实际上,非十进制 60进 在生活中也是有出现的,米、升这些单位本质上也和进制没区别。而从数学上也可以弄出 6进、12进、360进 这些奇葩但是有意义的计数法,它们主要用作某些大周期/位置关系的对照
  4. ^不是很严谨因为一些机器上整型数负溢出会回滚到极大值,但这里“递减”其实指的是除2 ,搞不好舍入真的能除回零
  5. ^这是因为 (log2 16) 是整数,而八进制类同 2**3=8 所以也直接对应
  6. ^当然你也可以用类似的“位移动”思路来做小数部分的读取换算,不过那就是乘基数,比如10,然后取整数部分,正向拼接成文本,因为是左取左移(如乘2取整) 就无需逆序了;注意是靠取整拿位所以比起整数部分,乘法在先
  7. ^顺便说一句, 0x 0b 是计算机语言通用的十六进、二进表示法, x 是 hexa-decimal 的缩写、 b 是 binary 的缩写;o 是 octal 的缩写。 汇编时代是 FAh 这样的后缀记法,0x 好像是 C 引入的,真是个好设计
  8. ^我大意了啊,用反了,没有闪。年轻人不讲武德,显然是有备而来,你好自为之。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值