learn.lianglianglee.com/专栏/程序员的数学课/01 从计数开始,程序员必知必会的数制转换法.md.html
2022-09-06 22:30:37 +08:00

365 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>01 从计数开始,程序员必知必会的数制转换法.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/程序员的数学课/00 开篇词 数学,编程能力的营养根基.md.html">00 开篇词 数学,编程能力的营养根基</a>
</li>
<li>
<a class="current-tab" href="/专栏/程序员的数学课/01 从计数开始,程序员必知必会的数制转换法.md.html">01 从计数开始,程序员必知必会的数制转换法</a>
</li>
<li>
<a href="/专栏/程序员的数学课/02 逻辑与沟通,怎样才能讲出有逻辑的话?.md.html">02 逻辑与沟通,怎样才能讲出有逻辑的话?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/03 用数学决策,如何规划好投入、转化和产出?.md.html">03 用数学决策,如何规划好投入、转化和产出?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/04 万物可数学,经典公式是如何在生活中应用的?.md.html">04 万物可数学,经典公式是如何在生活中应用的?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/05 求极值:如何找到复杂业务的最优解?.md.html">05 求极值:如何找到复杂业务的最优解?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/06 向量及其导数:计算机如何完成对海量高维度数据计算?.md.html">06 向量及其导数:计算机如何完成对海量高维度数据计算?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/07 线性回归:如何在离散点中寻找数据规律?.md.html">07 线性回归:如何在离散点中寻找数据规律?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/08 加乘法则:如何计算复杂事件发生的概率?.md.html">08 加乘法则:如何计算复杂事件发生的概率?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/09 似然估计:如何利用 MLE 对参数进行估计?.md.html">09 似然估计:如何利用 MLE 对参数进行估计?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/10 信息熵:事件的不确定性如何计算?.md.html">10 信息熵:事件的不确定性如何计算?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/11 灰度实验:如何设计灰度实验并计算实验的收益?.md.html">11 灰度实验:如何设计灰度实验并计算实验的收益?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/12 统计学方法:如何证明灰度实验效果不是偶然得到的?.md.html">12 统计学方法:如何证明灰度实验效果不是偶然得到的?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/13 复杂度:如何利用数学推导对程序进行优化?.md.html">13 复杂度:如何利用数学推导对程序进行优化?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/14 程序的循环:如何利用数学归纳法进行程序开发?.md.html">14 程序的循环:如何利用数学归纳法进行程序开发?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/15 递归:如何计算汉诺塔问题的移动步数?.md.html">15 递归:如何计算汉诺塔问题的移动步数?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/16 二分法:如何利用指数爆炸优化程序?.md.html">16 二分法:如何利用指数爆炸优化程序?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/17 动态规划:如何利用最优子结构解决问题?.md.html">17 动态规划:如何利用最优子结构解决问题?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/18 AI 入门:利用 3 个公式搭建最简 AI 框架.md.html">18 AI 入门:利用 3 个公式搭建最简 AI 框架</a>
</li>
<li>
<a href="/专栏/程序员的数学课/19 逻辑回归:如何让计算机做出二值化决策?.md.html">19 逻辑回归:如何让计算机做出二值化决策?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/20 决策树:如何对 NP 难复杂问题进行启发式求解?.md.html">20 决策树:如何对 NP 难复杂问题进行启发式求解?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?.md.html">21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/22 面试中那些坑了无数人的算法题.md.html">22 面试中那些坑了无数人的算法题</a>
</li>
<li>
<a href="/专栏/程序员的数学课/23 站在生活的十字路口,如何用数学抉择?.md.html">23 站在生活的十字路口,如何用数学抉择?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/24 结束语 数学底子好,学啥都快.md.html">24 结束语 数学底子好,学啥都快</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>01 从计数开始,程序员必知必会的数制转换法</h1>
<p>以前看过一个幽默段子,老师说:“世界上有 10 种人,一种懂二进制,另一种不懂二进制。”小琳问:“那另外 8 种人呢?” 显然小琳同学是不懂二进制的那类人。二进制的 10代表的是十进制的 2。替换到老师的话中就是世界上有两种人一种懂二进制另一种不懂二进制。</p>
<p>当我们还是个孩童时,幼儿园的阿姨便用火柴棍教我们如何数数。这是最早期的数学教育,这也是在某个数制下的计数问题。</p>
<p>作为第一节课,我还是想和你回归最基本的“数制转换”主题。我将以图文结合的方式,与你一起回顾温习数制,详解不同数制之间的巧妙联系,并重新思考数制与编程、计算机的关联。例如,如何利用二进制的位运算,对一个查找问题的代码进行优化等内容。</p>
<h3>数制</h3>
<p><strong>数制是一种计算数量大小的制度</strong>,也是计数法。用大白话来说,<strong>就是数数的方法</strong></p>
<p>数制中,最重要的因素是<strong>基数</strong>。假设我们设置基数为 10 来数数,那就是在用十进制计数法;如果设置基数为 2就是在用二进制计数法。</p>
<p>不同的数制中,使用最广泛的就是十进制,这与人类有 10 个手指头是密不可分的。人类在学习计数和四则运算时,会通过手指头辅助计算。</p>
<ul>
<li>在我国的古代,也曾经使用过十六进制。例如,成语半斤八两的含义是彼此不相上下,实力相当。即半斤就是 8 两1 斤就是 16 两。</li>
<li>在时间的计数场景时我们也用过二十四进制和六十进制。例如1 天等于 24 小时1 小时等于 60 分钟1 分钟等于 60 秒。</li>
</ul>
<h3>不同数制的表达</h3>
<p>有了不同的数制,就需要对数制下的数字进行区分,否则就会造成混淆。例如,象征考试得了满分的 100在十进制下依旧是 100而在二进制下它就是十进制下的 4在八进制则表示十进制下的 64在十六进制则表示十进制下的 256。</p>
<blockquote>
<p>至于为什么如此计算转换,下文的数制转换方法会详细讲解。</p>
</blockquote>
<p>所以如果对数字不加以说明,你会发现很难判断这到底是哪个数制下的数字,毕竟同一数字在不同数制下其意义是完全不同的。为了避免混淆,我们对不同数制下的数字做了区分。</p>
<p>十进制使用的数字符号是 [0,1,2,3,4,5,6,7,8,9];对于二进制和八进制,它们仍然沿用十进制的数字符号。在十六进制中,由于数字符号不够用,这就需要额外补充。一般用 [A,B,C,D,E,F](一般不会特别区分字母的大小写),分别代表十进制下的 [10,11,12,13,14,15]。</p>
<ul>
<li>一般而言,没有额外说明的数字都是十进制下的数字;</li>
<li>表示二进制时,会用 0b 作为数字的前缀;</li>
<li>表示八进制时,会用 0o 或者 0 作为数字的前缀;</li>
<li>表示十六进制时,会用 0x 作为数字的前缀。</li>
</ul>
<p>这里 b、o、x 三个英文字母的选择均来自数制的英文单词。</p>
<p>综上,我们对这几个数制的信息整理如下表:</p>
<p><img src="assets/CgqCHl-WYACATr-VAAC40XoQFZU944.png" alt="png" /></p>
<h3>数制转换的方法</h3>
<p>人们在使用数制进行计算时,都习惯性地把原问题映射到十进制中;计算完成后,再映射回去。这里就牵涉数制的转换啦。</p>
<p>我举一个生活中最常见的数制转换的例子。</p>
<blockquote>
<p>例如,上午 8:40 开始考试,考试时长是 40 分钟,问考试结束的时间是多少?</p>
</blockquote>
<p>计算过程是:考试时长的<strong>40 分钟</strong>加上 8 点过 40 分的<strong>40 分钟</strong>就是 80 分钟,也即是 1 小时 20 分钟,再加上 8 点本身,结束时间就是上午 9:20。</p>
<p>“40分钟+40分钟=80分钟”就是十进制的算术过程可见为了完成其他数制的运算我们依旧更喜欢用十进制做桥梁毕竟我们对十进制的运算是最熟悉的。</p>
<h4>1. 换基法(换向十进制)</h4>
<p>我们给出数制转换的定量方法,也就是对于任意一个基数 N 进制下的数字 X它转换为十进制的方法。如下图的公式所示原进制若是 N 进制,转换时的基数便取 N。例如将二进制的 X 转化为十进制时,运算时的转换基数便取为 2。</p>
<p><img src="assets/Ciqc1F-WYCCAOCMMAACEX2a1vNU222.png" alt="png" /></p>
<ul>
<li>我们举个例子,十进制下的 2020。</li>
</ul>
<p>它是十进制,所以我们基数便取 102020有 4 位数,根据上图公式,我们分别取(4-1)次方、(4-2)次方、(4-3)次方、(4-4)次方,再分别与每位数相乘,再相加取和。</p>
<p><img src="assets/Ciqc1F-WYCiAGzKgAABcMIWYJfA894.png" alt="png" /></p>
<ul>
<li>再举个例子,二进制下的 10110利用换基法转换为十进制。</li>
</ul>
<p>它原是二进制,所以我们基数便取 210110 有 5 位数,根据上图公式,我们分别取(5-1)次方、(5-2)次方、(5-3)次方、(5-4)次方、(5-5)次方,再分别与每位数相乘,再相加取和。</p>
<p><img src="assets/CgqCHl-WYDWAFnchAABhzdyMebE350.png" alt="png" /></p>
<h4>2. 除余法(十进制向其他进制转换)</h4>
<p>转向的目标进制为 N 进制,则以 N 为除数不断地做除法,将最后的商和之前的余数<strong>逆序</strong>串联在一起,就是最终的结果。</p>
<p>例如,十进制的 19 转换为二进制的过程如下图所示:</p>
<p><img src="assets/CgqCHl-WYEKAWp8MAABzhu_bTgE812.png" alt="png" /></p>
<p>用 19 对 2 做除法得到余数 1再用商对 2 做除法得到余数 1再用商对 2 做除法得到余数 0...直到商为 1 结束。最终用最后的商也就是1和过程中所有的余数<strong>逆序</strong>串联在一起,就是最终的结果 10011。</p>
<p>值得一提的是,除余法除了适用于十进制向二进制的转换,也<strong>适用于十进制向任何数制的转换</strong>。例如,用除余法将十进制的 100转换为八进制和十六进制的计算过程如下得到结果分别是 0144 和 0x64。</p>
<p><img src="assets/Ciqc1F-WYEqAI9leAABAGGC65Co725.png" alt="png" /></p>
<p>我们可以给出个简单的证明,根据换基法我们知道某个数制 N 下的数字的十进制表示为:</p>
<p><img src="assets/Ciqc1F-WYFOAS0s2AABTeUEt0AY493.png" alt="png" /></p>
<p>其中Xm、Xm-1、...、X1 分别为数字 X 在 N 进制下的每一位数字,也是我们要求解的目标。接着,我们可以计算 X 除以 N。</p>
<p>这样可以得到,当我们第一次对 N 做除法时,就可以得到商为 N 进制下的 XmXm-1Xm-2...X2余数就是 X1
<img src="assets/CgqCHl-ZLnKAYGn9AACGBxBvaeQ751.png" alt="png" />
那么第一次除以 N是如何得到商为 N 进制下的 XmXm-1Xm-2...X2余数就是 X1 的呢?你可以通过下图这个 16 进制下的 5321 这个例子理解。
<img src="assets/Ciqc1F-aLESAM0L2AAITpDWjzuk862.png" alt="png" />
这里以 16 进制下的 5321 为例,可以更好地理解这一过程。如果不带入具体数制下的数字,你也可以通过公式推导出来,只是不那么容易理解,不过你自己也可以尝试。</p>
<p>接着同理,我们再用上一步的商 XmXm-1Xm-2...X2 重复对 N 做除法的过程,就会得到新的商为 N 进制下的 XmXm-1Xm-2...X3 ,余数为 X2 。再同理,重复上面的过程,你会发现得到的余数分别是 X1X2X3...Xm。</p>
<p>最后,我们把所有的余数做个逆序,就得到了 N 进制下的 X 的每一位,最终就能得到 XmXm-1Xm-2...X1 了。</p>
<h4>3. 按位拆分法和按位合并法</h4>
<p>对于八进制和二进制之间的转换,你可以利用十进制做个跳板。</p>
<p><strong>除此之外,还有一个简单的按位拆分法,可以将八进制转为二进制。</strong></p>
<p>你只需要把原来八进制中的每个数字符号,直接拆分为 <strong>3 位的二进制</strong>数字符号(必须保证是 3 位),再按<strong>顺序</strong>串联起来,就是最终结果。</p>
<p>我们以八进制下的 023 为例进行讲解:</p>
<ul>
<li>由于十进制的 2 的二进制表示是 010</li>
<li>十进制的 3 的二进制表示是 011</li>
<li>最后,别忘加上二进制的符号 0b并去掉首位 0。</li>
</ul>
<p>则八进制的 023 的二进制表示就是 0b10011如下图</p>
<p><img src="assets/CgqCHl-WYbSARzuZAABfRNDl43E120.png" alt="png" /></p>
<p><strong>同理,二进制转换为八进制,可以采用每 3 位合并的按位合并法。</strong></p>
<p>如下图,二进制的 0b10011 转换为八进制,则<strong>从后往前</strong>每 3 位合并:</p>
<ul>
<li>最后 3 位是 011它是十进制的 3在八进制也用 3 表示;</li>
<li>从后往前的两位是 10不够三位时补“0”则为 10它是十进制的 2在八进制也用 2 来表示;</li>
<li>别忘加上八进制的符号 0o。</li>
</ul>
<p>则最终八进制的结果就是 0o23 或 023。</p>
<p><img src="assets/CgqCHl-WYb2AWnlDAABXQI-u4nk588.png" alt="png" /></p>
<p>对于<strong>十六进制和二进制之间的转换</strong>,也可以采用按位合并和按位拆分的方法,区别只是在于需要按<strong>4 位</strong>进行合并或拆分。</p>
<p>例如下图,十六进制的 0x1a 转换为二进制,由于 1 为 0001a 为 1010串联在一起之后二进制的结果就是 0b11010。</p>
<p><img src="assets/Ciqc1F-WYcSAbeioAABXFe3nLms882.png" alt="png" /></p>
<p>同样地,二进制的 0b1011101 转换为十六进制,从后往前每 4 位合并:</p>
<ul>
<li>最后 4 位是 1101它是十进制的 13在十六进制表示为 d</li>
<li>往前的几位是 101十进制和十六进制都用 5 来表示;</li>
<li>别忘加上十六进制的符号 0x。</li>
</ul>
<p>则最终十六进制的结果就是 0x5d。
<img src="assets/CgqCHl-XgvyAPLFvAABXUh2bCnY315.png" alt="png" /></p>
<p>为何八进制与二进制的转换是按照 3 位数合并、拆分,而十六进制与二进制之间则是 4 位数呢?本质原因是在于 2³=8 和 2⁴=16。根据这表达式可以看出二进制中的 3 个 bit恰好可以表示 07 这 8 个数字。因此,按照 3 位合并,就可以从二进制转化到八进制了。同理,按照 4 位合并,就可以从二进制转化到十六进制了。</p>
<p>而八进制与十六进制之间的转换,就不适用按位合并和按位拆分的方法了,你可以以二进制或十进制为跳板,进行两者之间的转换。</p>
<h4>4. 数制转换图</h4>
<p>我们总结一下,对于一般的数制之间转换,我们喜欢以十进制来作为跳板。</p>
<p>其他数制向十进制的转换方法是<strong>换基法</strong>,而十进制向其他数制转换的方法是<strong>除余法</strong></p>
<p>特别地,对于程序员经常关注的二进制、八进制和十六进制之间,它们又有一些特殊的转换方法。二进制向八进制或十六进制的转换,可以采用<strong>按位合并法</strong>;八进制或十六进制向二进制的转换,可以采用<strong>按位拆分法</strong></p>
<p><img src="assets/Ciqc1F-WYpOAE9r4AAHd7gv5pPI247.png" alt="png" /></p>
<p>数制转换方法图</p>
<h3>数制转换与编程</h3>
<p>在编程的时候,利用对不同数制及其转换的性质,往往能让很多复杂问题迎刃而解。最常见的就是二进制下的运算,看下下面的例题。</p>
<h4>【例题】判断一个整数 a是否是 2 的整数次幂。</h4>
<p>解析:如果是十进制,判断一个数是否是 10 的整数次幂只需要看这个数字的形式是否为一个“1”和若干个“0”构成。例如一个“1”和两个“0”构成“100”它是 10 的 2 次幂一个“1”和 4 个“0”构成“10000”它是 10 的 4 次幂。</p>
<p>因此这个题目的解法就是,把 a 转换为二进制,看看 bin(a) 的形式是否为一个“1”和若干个“0”构成代码如下</p>
<pre><code>a = 8
b = str(bin(a))
total = 0
for i in range(2,len(b)):
total += int(b[i])
if total == 1 and b[2] == '1':
print 'yes'
else:
print 'no'
</code></pre>
<p>我们对代码进行解读。</p>
<ul>
<li>第 12 行,变量 a 为待判断的整数;变量 b 是 a 的二进制形式,并且被我们强制转化为 string 类型,这样 b 的值就是 0b1000。</li>
<li>如果形式为一个“1”和若干个“0”则需要满足以下两个性质第一首位为“1”第二所有位加和为“1”。</li>
<li>在代码中,第 46 行,我们计算了所有位数的加和,并保存在 total 变量中。</li>
<li>在第 811 行,我们根据两个性质,对结果进行判断,并打印 yes 或者 no。</li>
</ul>
<p>我们还可以利用<strong>位运算的“与”</strong>,来判断二进制数字 x 的形式是否为一个“1”和若干个“0”。判断的方法是计算 x &amp; (x-1),如果结果为 0 则是,如果结果非 0 则不是。这样我们可以得到更简单的实现代码,代码如下:</p>
<pre><code>a = 80
if a &amp; (a-1) == 0:
print 'yes'
else:
print 'no'
</code></pre>
<p>其中涉及关于位运算的知识,我会在下一个课时进行详细剖析。</p>
<h3>小结</h3>
<p>数制是数字的基础,也是计算机的基础。信息时代的到来,让二进制被广泛应用,这主要是因为电路中的开关只有接通和切断两种状态,二进制的运算也称为位运算。</p>
<p>计算机的数据存储单位便体现了数制的应用,计算机中的数据存储单位常常用 Byte字节或 bit</p>
<p>bit 是表示信息的最小单位,叫作二进制位,一个 bit 等于一个二进制数。一个十进制的数的比特要换成二进制看,比如十进制 31 换二进制是 11111 是 5 个 bit32 换二进制是 100000 是 6 个 bit。而 Byte 叫作字节用于表示计算机中的一个字符是计算机文件大小的基本计算单位1 Byte = 8 bit也写作 1B = 8b它采用了 8 个 2 进制位。</p>
<p>在本课时中,我们学习不同数制之间的转换方法,包括换基法、除余法、按位拆分法和按位合并法。其中的换基法和除余法,是关于十进制的转换;而按位拆分法和按位合并法,则是关于二进制的转换。</p>
<p>在学习过程中,你会发现八进制和十六进制采用的按位合并法,更像是对二进制的压缩表示。八进制或十六进制的一个位,可以表示出 3 或 4 位的二进制数字。因此,用八进制或十六进制来表示二进制会更为方便。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/程序员的数学课/00 开篇词 数学,编程能力的营养根基.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/程序员的数学课/02 逻辑与沟通,怎样才能讲出有逻辑的话?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997bbdaaaa3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>