美文网首页
JS基础-数字-0.1+0.2!=0.3(一)

JS基础-数字-0.1+0.2!=0.3(一)

作者: 火锅伯南克 | 来源:发表于2021-03-08 22:46 被阅读0次

    本系列只适用于,对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 102 + 1 x 101 + 3 x 100,
    每个数字都具备位值赋予的值。从右向左,第一个要乘以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 23 + 0 x 22 + 1 x 21 + 1 x 20
    = 8 + 0 + 2 + 1
    = 11

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

    如果 1011 是10进制,是不是可以做如下分解:
    = 1 x 103 + 0 x 102 + 1 x 101 + 1 x 100
    = 1000 + 0 + 100 + 1
    = 1011

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

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

    这个问题等价于 1000000 这个二进制数的量为多少。我们习惯于10进制,那么就转化成10进制吧。
    = 1 x 27 + 0 x 26 + 0 x 25 + 0 x 24 + 0 x 23 + 0 x 22 + 0 x 21 + 0 x 20
    = 128

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

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

    Ⅰ、3进制 2021 转化成 4进制:
    2021(3) 先转 10进制
    = 2 x 33 + 0 x 32 + 2 x 31 + 1 x 30
    = 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 72 + 6 x 71 + 4 x 70
    = 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 102 = 71.0
    71.0 ÷ 10 = 7.1

    从中可以发现规律,
    10进制的数:
    乘10,以小数点为基准,整体左移一位,
    乘102,以小数点为基准,整体左移两位。
    乘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 24 =11010.0
    11010.0 x 2-6 = 0.01101

    可以发现,2进制的数
    乘2,以小数点为基准,整体左移一位,
    乘24,以小数点为基准,整体左移四位,不够位补零。
    乘 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、左操作数不能大于 232 - 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)。

    还没完,因为虽然等式两边同时乘除一个数,等式不变,但右边相对于原来的值是扩大了 25 倍,需要把这个倍数去掉,还记得移位这个东西吗?
    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 的部分“秘密”,不过这只是冰山一角,后续会继续深入说明。

    相关文章

      网友评论

          本文标题:JS基础-数字-0.1+0.2!=0.3(一)

          本文链接:https://www.haomeiwen.com/subject/qltztltx.html