JS基础-数字-0-1+0-2!=0-3(一)

本系列只适用于,对0.1+0.2!=0.3这个问题“深恶痛绝”的人。

本章是关于 进制 的说明,如果你已经熟悉进制规则,请直接跳过本章,如果本章没有帮助到你理解进制,请搜索其他相关文章、资料。总之,关于进制的原理一定要熟悉,否则后续说明都没有意义。

##数量,数值,数字 如果我指着这张图说“左边是5,右边也是5”,大多数人都领会我说的是5个苹果和5只兔子,但思维缜密的人会想,会不会是“5元一斤”?所以,为了清楚表达,还需要加上量词和名词,“5个苹果”和“5只兔子”。

“5个”和“5只”为数量,抽离出两者数量的内在本质—“量”为5,把量再进一步抽象,用数目表示出来多少,叫做这个量的数值,为5(数学课本理论部分中的数字为数值,注意,数值是没有计量单位的),为了便于传播,对数值进行书写,可以为 5 (阿拉伯数字,古印度人发明)、五或伍(中文数字)、five(英文数字)、V(罗马数字)等。

数字属于文字的一种,所以本质也是符号,是一种映射。

如果你还有印象,在小学第一次接触数字时,一定是比文字更难懂的,更难学习,因为数字也是文字符号的一种,文字就是一种抽象,这在《闲聊编码与加密》中已经说过,所以这具备了第一层抽象,但数字又包含了从“量”到“数值”的一层抽象,综合下来,数字就拥有2层抽象,当然要难以理解一些。

由于我们习惯使用10进制,通常会把看到的“数字”都等价于10进制的“数字”(比如数学课本描述的数字,在没有特殊说明下,都是10进制的),但“数字”是相对独立的,其本身只是把“数值”实例化到纸张或者其他载体的一种手段,和进制本没有关系,完全可以使“数字”代表多种进制,出现这种情况的原因是我们在学习、生活中,潜移默化的把10进制和“数字”绑定在了一起,这种思维需要破除,把10进制和“数字”解绑。

##位值原则 数值113(阿拉伯数字)的多种符号表示方法:

中文数字:一百一十三(壹佰壹拾叁) 英文数字:one hundred and thirteen 罗马数字:CXIII 阿拉伯数字:113

位值原则:每一个数字出了表示自身的值之外,还有一个所在位置所赋予的值,即位值。

阿拉伯数字113 = 1 x 10^2^ + 1 x 10^1^ + 3 x 10^0^, 每个数字都具备位值赋予的值。从右向左,第一个要乘以10的0次方,第二个要乘以10的1次方,第三个要乘以10的2次方,以此类推。可见书写方便,易于理解

中文数字、英文数字、罗马数字都不具备着这个特性,是阿拉伯数字首开先例。不要小看位值原则,以为它很平常。在历史上,位值原则是杰出而重要的思想,是人类文明重要的里程碑之一,也是数学史上无与伦比的一个光辉成就。马克思曾经高度地评价过位值原则的出现,称赞它是“最美妙的数学发明”。

并不是阿拉伯数字的推广,位值原则才显得重要,而是因为位值原则重要,阿拉伯数字才得以被推广,所以以下都已阿拉伯数字为主说明。

##进制 由于我们从小接触10进制或者说一直使用10进制,自然会认为10进制是最好的进制。就如说“PHP是世界上最好的语言”的人一样,是否也只是使用PHP过多或者只会PHP这一门语言的人说所的呢?语言没有好坏之分,只有是否适用之别,进制也一样。

原始人在记录猎物数量时,会伸出10个手指头,每记录1个猎物,就掰弯一个手指,10个手指都掰弯了,就在地上放一个小石块(或者别的什么东西),10进制就是这么来的。

身边的进制 时间:

60进制: 60秒 = 1分钟, 60分钟 = 1小时 (古巴比伦留给我们的遗产) **24进制:**24小时 = 1天 **10进制:**10天 = 1旬(旬代表年龄时,1旬为10年,“年过五旬”就是过了50岁) **7进制:**7天 = 1周 **3进制:**3旬 = 1月,3月 = 1季 **4进制:**4季 = 1年 12进制: 12月 = 1年

其他:

**16进制:**秦统一度量衡时,李斯主张制定16两 = 1斤,沿用至解放前。成语“半斤八两”出处就在这,意思是两人实力差不多。 在欧洲,1磅 = 16盎司,1俄尺 = 16俄寸。 2进制: 《易经》中的说:无极生太极,太极生两仪,两仪生四象,四象生八卦,就是2进制,莱布尼茨的2进制与此没有直接关系,但有间接影响。

为什么出现这么多的进制,都是根据具体需求来的,具体是什么需求就要你自己查阅总结了。

一个进制计数方法如果想记录,几进制就要有几个不同的符号对值进行表示:

**2进制:**0,1 **10进制:**0,1,2,3,4,5,6,7,8,9 **16进制:**0,1,2,3,4,5,6,7,8,9,a(代表10),b(代表11),c(代表12),d(代表13),e(代表14),f(代表15)

至于为什么16进制使用英文字母代表10以上的数字,可能是因为英文字母的普及率比较高和书写方便。

2进制就是逢2进1,10进制值就是逢10进1,16进制逢16进1。

之所以逢几进1,就是因为2进制没有代表2的符号,10进制没有代表10的符号,以此类推,如果要用2进制计数,就得这样数数了:

**2进制计数:**1,10,11,100,101,110,111,1000 ....

【为了区分,通常会在数值后面加一个小括号,里面的值就是进制数】虽然 5(10)101(2) 在书写上不同,但是他们代表的 “量” 是相同的,如果把它们 “量” 化,就是都代表这么多个苹果。 一堆苹果

既然他们都代表相同的 “量” ,就说明它们之间可以通过某种方法进行转化。

一、10进制 转 2进制方法 1.等式方法 2.竖式方法

注意:书写数字时时从左往右写,但是,位数是从右往左数的,右数第一个数字时第一位,右数第二个数字时第二位....以此类推,所以最右面的是最低位,最左边的是最高位。一个16位的2进制,如: 11111111 00000001 中,11111111就是所谓的高八位,00000001就是低八位。

这两种方法都是 “二除取余法”,你喜欢哪一种就用哪一种就好,效果都是一样的,需要注意的是,两种都是商为0时停止,结果是顺着箭头方向书写。

再转化一个,把 10进制的6 转化成2进制。

所以6转化成10进制为:110

以上工具可在github下载:https://github.com/fengjinlovewei/ieee754.git

举个栗子说明: 假设我玩一款带有装备合成系统的游戏,

合成规则是:同类型的武器,2把一星可以合成1把二星,2把二星可以合成1把三星,2把三星可以合成1把四星....以此类推。

现在我已经收集了11把一星的武器,如下: 11把一星的武器 那么我最高能合成一把几星的武器呢?

这个得一步一步来,我首先要知道我能合成几把二星武器?

11 ÷ 2 = 5 ········ 1,那么现在我手里有了5把二星,并且剩下1把一星了,这个一星就暂且不管它了,因为已经无法合成了。 合成二星

然后我需要合成三星, 5 ÷ 2 = 2 ········ 1,现在我有2把三星,并且剩下一把二星,这个2星我也不管它了。 合成三星

现在合成四星, 2 ÷ 2 = 1 ········ 0,现在我有1把四星了,没有剩下多余的三星。

合成四星 合成五星, 1÷ 2 = 0 ········ 1,很遗憾,合成不了五星了,因为不够了。

现在我的游戏包裹里很清爽了,如下: 包裹里现有的装备

这个是不是和2进制有那么点联系呢?是不是和刚才计算过程一致? 那就再直白一点吧,星级数就代表位数,这个位数是从右往左数的,一星占位······右1,二星占位······右2,四星占位······右4,右3空置,就补0 星值对应着位数,没有位的补0 通过着这个例子,应该可以知道为什么10进制转化2进制要用 **“二除取余法”**了吧。

如果现在游戏规则改了,要3把同类型武器合成更高星武器,实际上就是使用了3进制原理: 合成二星: 11 ÷ 3 = 3 ········ 2,合成3把二星,剩下2把一星

合成三星: 3 ÷ 3 = 1 ········ 0,合成1把三星,没有剩下二星

合成四星: 1 ÷ 3 = 0 ········ 1,合成不了四星,因为不够。

这个就是 3进制 啦。10进制的数值11 = 三进制的数值 102

二、2进制 转 10进制方法

如果把2进制的数值 1011 转化成10进制的数值,方法是: = 1 x 2^3^ + 0 x 2^2^ + 1 x 2^1^ + 1 x 2^0^ = 8 + 0 + 2 + 1 = 11

如果是3进制的 1011 转化成10进制呢? = 1 x 3^3^ + 0 x 3^2^ + 1 x 3^1^ + 1 x 3^0^ = 27 + 0 + 3 + 1 = 31

如果 1011 是10进制,是不是可以做如下分解: = 1 x 10^3^ + 0 x 10^2^ + 1 x 10^1^ + 1 x 10^0^ = 1000 + 0 + 100 + 1 = 1011

同样都是 “数字” 1011 ,因为进制不同,所以代表的 “数值”(也可以叫做 “量”)也就不同,因为每个数字因为位置的不同,所具备的隐藏属性不同,这就是 “位值原则” 在发挥作用。

如果游戏规则为:两把同星级武器可以合成一把高一级武器,我要合成1把七星的武器,至少需要多少把一星武器呢?

这个问题等价于 1000000 这个二进制数的量为多少。我们习惯于10进制,那么就转化成10进制吧。 = 1 x 2^7^ + 0 x 2^6^ + 0 x 2^5^ + 0 x 2^4^ + 0 x 2^3^ + 0 x 2^2^ + 0 x 2^1^ + 0 x 2^0^ = 128

细心同学应该发现,上面例子中都是10进制转其他进制,那么如果 3进制4进制,或者 7进制5进制怎么转呢?

最简单的方法就是把3进制和7进制转化成10进制,再转转化成 4进制5进制,但是如果你的脑子中对于 “量” 的把控已经炉火纯青了,也可以直接转化。不过我还没到达这个境界,还是按简单的方法来吧

Ⅰ、3进制 2021 转化成 4进制: 2021(3) 先转 10进制 = 2 x 3^3^ + 0 x 3^2^ + 2 x 3^1^ + 1 x 3^0^ = 54 + 0 + 6 + 1 = 61

61(10) 再转 4进制 61 ÷ 4 = 15 ········ 1 15 ÷ 4 = 3 ········ 3 3 ÷ 4 = 0 ········ 3 (商为0,停止) 最终,3进制 2021 转化成 4进制为:331

Ⅱ、7进制 164 转化成 5进制: 164(7) 先转 10进制 = 1 x 7^2^ + 6 x 7^1^ + 4 x 7^0^ = 49 + 42 + 4 = 95

95(10) 再转 5进制 95 ÷ 5 = 19 ········ 0 19 ÷ 5 = 3 ········ 4 3 ÷ 5 = 0 ········ 3 (商为0,停止) 最终,7进制 164 转化成 5进制为:340

问题1:2进制的数值1 和 10进制的数值1,哪个“量”更大? 问题2:8进制的数值7 和 10进制的数值7, 哪个“量”更大? 当然是一样大。

三、其他:

汉字的数字

内外骚动,怠于道路,不得操事者,七十万家。——《孙子兵法》用间篇

《孙子兵法》于春秋时著(即自公元前770年至公元前476年这段历史时期,阿拉伯数字0,1,2,3,4,5,6,7,8,9起源于公元500年前后,相差近1000年)。可见在春秋时,我国就已经使用一、二、三、四、五、六、七、八、九、十、百、千、万的数字,从十以后,如:一十三,二十七。数字符号出现重复性,可见古人很早就使用了10进制计数法

比如记录117:一百一十七。 这与位值原则已经很接近了,试着去掉单位:一一七,这是不是就能使用位值原则了呢,感觉从 一百一十七 进化成 一一七 应该是一件顺理成章的事情,可为什么没有这样发展呢? 举个例子说明为什么没有,现记录700:七百,如果要使用位值原则,就必须要写成这样 七OO ,问题就出在这,我们的古人计数没有 O 的概念(注意这个O它是一个汉字!阿拉伯数字在元代传入我国之后,人们参照其中的0,创造了汉字“O”,例如“二〇〇九年”。从书写来说,写“〇”比写“零”要方便得多。因此,大家俗称它为“小写的零”。),但没有O,就没有占位符,没有占位符,像七百这样的数字就无法体现位值原则,可以说我们的古汉字数字与位值原则失之交臂,就是因为没有 O, 不过这都是我自己猜的。。

再来看看英文的数字

1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 eight 9 nine 10 ten 11 eleven 12 twelve

13 thirteen 14 fourteen 15 fifteen 16 sixteen 17 seventeen 18 eighteen 19 nineteen

20 twenty 21 twenty-one 22 twenty- two 23 twenty- three 24 twenty- four 25 twenty- five 26 twenty- six 27 twenty- seven 28 twenty- eight 29 twenty- nine

30 thirty 31 thirty- one 32 thirty- two 33 thirty- three 34 thirty- four 35 thirty- five 36 thirty- six 37 thirty- seven 38 thirty- eight 39 thirty- nine

40 forty ....

不知道你的英语老师有没有给你解释过“为什么英语的数字重复词根出现13,而在20以后又强行改成了10个数字为重复词根”呢,这其实就是欧洲最早是使用12进制计数的缩影,在美国,至今仍有一个“美国十二进制协会”,公开申明致力于十二进制的推广普及工作,理由是12比10拥有更多的因数,一会12进制,一会又10进制,英文数字如此任性,更不可能进化出位值原则。

如果我国数字与位值原则失之交臂,那欧洲数字与位值原则就是南辕北辙。

四、纯小数的进制转换

整数部分是零的小数叫做纯小数(pure decimal),如0.3,0.48,0.999等。

以下提到的小数都代指纯小数。

首先得明确一点,无论是几进制的小数,他都是一个大于0,小于1的数,或者直接说是纯分数(小于1的分数),他们的值都是基于量“1”的。 假设一块披萨的量为 “1”, 那么我吃了一半,还剩多少? 半块披萨 使用10进制小数来表示,那就是0.5了,这个都知道,因为 可以看成把披萨平均分成10分,取其中的5份,0.5 x 2 = 1。

二进制相当于把披萨平均分成2份,取其中的一份,概念容易懂,但是这个一份具体用数值怎么表示?

即便是小数,也要遵循进位的原则,比如 10进制的 0.5 x 2 = 1,就是因为5 x 2 = 10,要进位,所以变成了1.0 ,也就是1。 那么同样,2进制也要逢2进一,且两个加数要相等,结果只能是 0.1(2) + 0.1(2) = 1

所以, 10进制的 0.5 = 2进制的 0.1,因为他们都代表同一个“量”(半块披萨)

如果我又吃掉了半块披萨的一半, 那么此时剩余的披萨用十进制小数表示就是 0.5的 一半, 0.25。 用2进制表示就是 0.1的一半,那就是 0.01,因为 0.01 + 0.01= 0.1

所以, 10进制的 0.25 = 2进制的 0.01,因为他们都代表同一个“量”(半块披萨的一半,也就是四分之一披萨)

综上所述,可推出如下10进制和2进制小数对照表

1 = 1(2) = 1披萨(量) 0.5 = 0.1(2) = 1/2披萨(量) 0.25 = 0.01(2) = 1/4披萨(量) 0.125 = 0.001(2) = 1/8披萨(量) ......

那么,2进制小数转10进制小数方法如下: 例: 0.1011(2) = 1 x 2^-1^ + 0 x 2^-2^ + 1 x 2^-3^ + 1 x 2^-4^ = 0.5 + 0 + 0.125 + 0.0625 = 0.6875

注意其中的数值含义,比如1 x 2^-1^,1是它所在的2进制位置上的值,代表半块披萨的量,2^-1^代表10进制的0.5,也代表半块披萨的量,要转化成10进制,就需要用10进制的值去替换,总结一句话:在这个位置上,有1个10进制2^-1^的量。

把各个位置上的量相加,就是用10进制值,代表的2进制的值,的量。

#####10进制小数转化2进制小数又该怎么转呢? 先用10进制的例子做一些铺垫:

10进制 0.071 x 10 = 0.71 0.71 x 10^2^ = 71.0 71.0 ÷ 10 = 7.1

从中可以发现规律, 10进制的数: 乘10,以小数点为基准,整体左移一位, 乘10^2^,以小数点为基准,整体左移两位。 乘10^-1^,以小数点为基准,整体右移一位。 ..... 那么2进制的呢? 2进制 0.01 x 2 =0.1,因为0.01 + 0.01 = 0.1,上面已经说过了。

再举几个栗子: 0.1101 x 2 =1.101 1.101 x 2^4^ =11010.0 11010.0 x 2^-6^ = 0.01101

可以发现,2进制的数 乘2,以小数点为基准,整体左移一位, 乘2^4^,以小数点为基准,整体左移四位,不够位补零。 乘 2^-6^,以小数点为基准,整体右移六位,不够位补零。

n进制的数,乘以n的 ±m 次方时, m为正数时,以小数点为基准,向左移动m位,不够位补零。 m为负数时,以小数点为基准,向右移动m位,不够位补零。

js中的移位运算(>>,<< 二元操作符)也是这个原理: 比如运算 5 << 2 1、把 5转化成2进制,101(2) 2.、101(2) 左移 2位 得 10100, 相当于把 5 乘了2次的2,得20.

不过要注意的是: js的在进行>>,<<操作时,要先把左操作数转化成32位整型,所以左操作数就包含了两条隐藏属性: 1、左操作数不能大于 2^32^ - 1 这个数,如果超出,该数值的二进制高位部分超过32位的会直接舍弃掉。 例如:2 ** 32 + 4 >> 0 = 4 2、左操作数如果是带小数的,小数部分也会直接舍弃,无论大小 例如:5.9 >> 0 = 5

现在要把0.84375转化成10进制,可以写成如下等式:

#####a x 2^-1^ + b x 2^-2^ + c x 2^-3^ + d x 2^-4^ + ..... = 0.84375

左边是一个2进制转10进制的算式,右边就是这个算式的结果。 其中,a、b、c、d、e、f、g.....等未知数的取值范围为0或者1。

现在等式两边同时 乘 2,等式两边依旧相等:

#####(a x 2^-1^ + b x 2^-2^ + c x 2^-3^ + d x 2^-4^ + ..... ) x 2 = 0.84375 x 2

#####a.0 + (b x 2^-1^ + c x 2^-2^ + d x 2^-3^ + ..... ) = 1.6875

注意,此时a已经移动到了小数点的左边。

继续两边乘以2, #####ab.0 + (c x 2^-1^ + d x 2^-2^ + e x 2^-3^ + ..... )= 3.375

继续乘以2, #####abc.0 + (d x 2^-1^ + e x 2^-2^ + f x 2^-3^ + ..... )= 6.75

继续乘以2, #####abcd.0 + (e x 2^-1^ + f x 2^-2^ + g x 2^-3^ + ..... )= 13.5

继续乘以2, #####abcde.0 + (f x 2^-1^ + g x 2^-2^ + h x 2^-3^ + ..... )= 27.0

此时停止,因为10进制已经没有小数了,所以2进制也就没有了,这点希望你能想的通,转换完成。 abcde(2) = 27 10进制的27转化成2进制我们是会的,11011(2), 所以的等式右边的值为11011(2)。

还没完,因为虽然等式两边同时乘除一个数,等式不变,但右边相对于原来的值是扩大了 2^5^ 倍,需要把这个倍数去掉,还记得移位这个东西吗? 11011(2) x 2^-5^ = 0.11011(2),右移5位。

所以结果为 0.11011

但是这和网上流传(或者老师上课教)的方法不太一样,他们的方法是这样的: 你能查到的10进制小数转2进制小数的方法

这个转换方法非常干练,是提炼出的精华。

拿第一次乘2的等式举例说明: #####(a x 2^-1^ + b x 2^-2^ + c x 2^-3^ + d x 2^-4^ + ..... ) x 2 = 0.84375 x 2 #####a.0 + (b x 2^-1^ + c x 2^-2^ + d x 2^-3^ + ..... ) = 1.6875

#####a.0 + (b x 2^-1^ + c x 2^-2^ + d x 2^-3^ + ..... ) = 1.0 + 0.6875 此时,a = 1已经求出,左右两边同时减1,再乘以2

#####b.0 + (c x 2^-1^ + d x 2^-2^ + e x 2^-3^ + ..... ) = 1.0 +0.375 此时,b = 1已经求出,左右两边同时减1,再乘以2

#####c.0 + (d x 2^-1^ + e x 2^-2^ + f x 2^-3^ + ..... ) = 0 + 0.75 此时,c = 0已经求出,再乘以2

#####d.0 + (e x 2^-1^ + f x 2^-2^ + g x 2^-3^ + ..... ) = 1.0 + 0.5 此时,d = 1已经求出,左右两边同时减1,再乘以2

#####e.0 + (f x 2^-1^ + g x 2^-2^ + h x 2^-3^ + ..... )= 1.0 此时,e = 1已经求出,右边已经没有小数部分了,所以 (f x 2^-1^ + g x 2^-2^ + h x 2^-3^ + ..... )这一部分是不存在的。

这个转化就是精简方法基本一致了。 第一个方法求的是最终的结果。 第二个方法是直接求出未知数。

说这么多,就是想说明,流传的简单方法到底是怎么来的。

那么现在试着来求一下10进制的 0.1,和 0.2 转化成 2进制是什么样的吧,你会发现右边的小数部分永远也不能为1.0,也就是不能完全转化。

10进制0.1转化成2进制

10进制0.2转化成2进制

以上工具可在github下载:https://github.com/fengjinlovewei/ieee754.git

你可能已经发现了 0.1+0.2!=0.3 的部分“秘密”,不过这只是冰山一角,后续会继续深入说明。