[{"data":1,"prerenderedAt":447},["ShallowReactive",2],{"article:column:/column/math-beauty/finite-precision":3},{"id":4,"title":5,"body":6,"description":12,"extension":436,"meta":437,"navigation":442,"path":443,"seo":444,"stem":445,"__hash__":446},"column/column/math-beauty/finite-precision.md","0.1为什么不能总被机器准确写下",{"type":7,"value":8,"toc":427},"minimark",[9,13,16,81,84,87,91,94,97,100,103,106,109,112,119,122,125,128,131,134,137,140,143,149,152,155,158,162,165,168,171,174,177,180,183,186,189,192,196,199,221,224,227,230,236,239,247,250,254,257,337,340,343,346,349,352,358,361,364,370,373,376,379,390,399,408,417,420,423],[10,11,12],"p",{},"先修：07 二进制状态与计数；09 指数与对数；分数约分与位值记数",[10,14,15],{},"把0.1与0.2相加，结果当然是0.3。可是在常见的Python浮点环境里，下面两行会给出另一个样子：",[17,18,23],"pre",{"className":19,"code":20,"language":21,"meta":22,"style":22},"language-python shiki shiki-themes github-dark","print(0.1 + 0.2)         # 0.30000000000000004\nprint(0.1 + 0.2 == 0.3)  # False\n","python","",[24,25,26,56],"code",{"__ignoreMap":22},[27,28,31,35,39,42,46,49,52],"span",{"class":29,"line":30},"line",1,[27,32,34],{"class":33},"sDLfK","print",[27,36,38],{"class":37},"s95oV","(",[27,40,41],{"class":33},"0.1",[27,43,45],{"class":44},"snl16"," +",[27,47,48],{"class":33}," 0.2",[27,50,51],{"class":37},")         ",[27,53,55],{"class":54},"sAwPA","# 0.30000000000000004\n",[27,57,59,61,63,65,67,69,72,75,78],{"class":29,"line":58},2,[27,60,34],{"class":33},[27,62,38],{"class":37},[27,64,41],{"class":33},[27,66,45],{"class":44},[27,68,48],{"class":33},[27,70,71],{"class":44}," ==",[27,73,74],{"class":33}," 0.3",[27,76,77],{"class":37},")  ",[27,79,80],{"class":54},"# False\n",[10,82,83],{},"这段示例在Python 3.12.14、底数为2且有效精度为53位的环境中得到上述输出。它展示了一个现象，但不能代替解释：机器究竟存了什么，又在哪一步与纸上的实数运算分开？",[10,85,86],{},"前面的最短路、拟合与优化证明，主要在约定的数学对象中进行。程序最终却要用有限位数承载这些对象。从这一章开始，我们把表示方式也纳入问题。",[88,89,90],"h2",{"id":90},"二进制的小数点后面是什么",[10,92,93],{},"十进制小数每往右一位，位权除以10。二进制同样如此，只是每次除以2：第一位代表1/2，第二位代表1/4，第三位代表1/8，依次继续。",[10,95,96],{},"例如二进制0.1011₂的含义是",[10,98,99],{},"1/2＋0/4＋1/8＋1/16＝11/16＝0.6875。",[10,101,102],{},"右下角的2提醒我们按二进制读。数字还是同一个数字，只是写法换了。二进制0.1₂表示的是1/2，不能与十进制0.1混读。",[10,104,105],{},"若小数点后只写k个二进制位，把各项通分到2ᵏ，就一定能写成m/2ᵏ，其中m是整数。反过来，只要一个正分数约分后的分母是2的某次幂，就能把分子写成二进制，再放好小数点，得到有限展开。",[10,107,108],{},"因此，一个约成最简形式的分数p/q能有限地写成二进制小数，当且仅当q是2的非负整数次幂。必要性也能严格说清：若p/q＝m/2ᵏ，就有p2ᵏ＝mq；因为p与q互质，q必须整除2ᵏ，所以不能含有2以外的质因子。",[10,110,111],{},"1/10的分母含有5，便不满足条件。它的二进制展开是0.0001100110011……，不能用有限小数位写完。多加一些位，只能换来更接近的近似，不会把一个无限展开变成有限展开。",[10,113,114],{},[115,116],"img",{"alt":117,"src":118},"二进制位权分别为二分之一、四分之一、八分之一和十六分之一；11/16可以有限表示，而1/10的约分分母含5","/collections-assets/math-beauty/ch35/01-binary-fractions.png",[10,120,121],{},"图1：判断能否有限展开，关键是约分后的分母。图中的四位只是示例，证明覆盖任意有限位数。",[10,123,124],{},"这并不是说计算机无法保存精确的1/10。程序可以保存分子1与分母10，也可以选择十进制定点或十进制浮点格式。遇到限制的是“有限二进制小数”这种表示，不是所有可能的数据结构。[1]",[88,126,127],{"id":127},"浮点数把有限位数放在哪里",[10,129,130],{},"如果只固定小数点后几位，很小的数和很大的数会难以兼顾。浮点表示把一个数分成符号、有效数字和指数。普通的正二进制正规数可以写成",[10,132,133],{},"1.b₁b₂…b₍p−1₎ × 2ᵉ。",[10,135,136],{},"每个b都是0或1；p表示有效二进制位数，包括最前面的1；e控制小数点的尺度。这里的p只表示精度，不再表示上一节分数的分子。指数也只能使用格式允许的有限范围。[2]",[10,138,139],{},"先看一个只保留3个有效二进制位的教学格式，暂不触及指数边界。在[1，2)中，它能表示1.00₂、1.01₂、1.10₂、1.11₂，即1、1.25、1.5、1.75，相邻间隔为0.25。",[10,141,142],{},"把指数增加1，来到[2，4)，对应格点变成2、2.5、3、3.5，间隔扩大到0.5。在[4，8)中，间隔又变成1。有效位数没变，绝对间距却随数量级改变。",[10,144,145],{},[115,146],{"alt":147,"src":148},"三个有效二进制位的教学格式在1到2之间间隔四分之一，在2到4之间间隔二分之一，在4到8之间间隔1","/collections-assets/math-beauty/ch35/02-floating-grid.png",[10,150,151],{},"图2：三行采用同一数轴比例。每行只展开对应的数量级区间，右端点同时属于下一个区间；这种不均匀格点正是浮点表示的特征。",[10,153,154],{},"常见的IEEE 754 binary64格式，为正规数提供53个有效二进制位。这里说的是有效位，不是“小数点后53位”，更不是53位十进制小数。[1] Python可以用sys.float_info.radix与mant_dig检查运行环境的底数和精度。[3]",[10,156,157],{},"还要分清“有限展开”与“装得进指定格式”。1＋2⁻⁶⁰当然有有限二进制展开，但在1附近，binary64没有足够的有效位把这两项同时留下。另一个数即使只需要很少有效位，也可能因为指数过大或过小而超出范围。前一节的分母判据，没有替我们免除位数和范围限制。",[88,159,161],{"id":160},"落不进格点时怎样选择","落不进格点时，怎样选择",[10,163,164],{},"本章先采用舍入到最近格点的规则。若一个实数位于两个相邻格点之间，选距离较近的那个；恰在中点时，常用规则选择末位有效数字为偶数的格点，二进制中也就是末位为0。[2]",[10,166,167],{},"在刚才的3位教学格式中，1.1位于1与1.25之间，离1更近，于是保存为1。1.125恰在正中间，按中点取偶规则也选1：它的有效位100末位为0，另一个格点1.25的有效位101末位为1。",[10,169,170],{},"每一个浮点格点代表的值都是确定的。近似发生在把想表达的实数映射到格点时。选择最近值，可以约束这一次映射的误差，却不能保证许多步之后的最终误差仍然那么小。",[10,172,173],{},"把实数z经最近舍入后的值记为fl(z)。在精度为p的二进制正规区间[2ᵉ，2ᵉ⁺¹)中，相邻格点间距为2^(e−p＋1)。最近舍入的绝对误差至多是这个间距的一半，即2^(e−p)。再除以z≥2ᵉ，就得到",[10,175,176],{},"|fl(z)−z|/|z| ≤ 2⁻ᵖ。",[10,178,179],{},"负数可以按对称的绝对值情况处理。因此，若精确结果处于正规范围，采用最近舍入，且没有溢出等异常，可以写成",[10,181,182],{},"fl(z)＝z(1＋δ)，其中|δ|≤u，u＝2⁻ᵖ。",[10,184,185],{},"u叫单位舍入误差。binary64中p＝53，于是u＝2⁻⁵³。这是特定条件下一次舍入的相对误差界，不是任何程序的最终误差界。[2]",[10,187,188],{},"Python的sys.float_info.epsilon在这种环境中则是2⁻⁵²：它表示1与比1大的下一个可表示数之间的差，正好是这里u的两倍。[3] 两个量常在资料里使用相近名称，核对定义比只认“epsilon”这个词更可靠。",[10,190,191],{},"若发生下溢，出现靠近0的非正规数，前述相对界不能原样套用；溢出可能得到无穷大，未定义运算还可能产生NaN。0也不能放进相对误差的分母。讨论这些边界时，需要回到具体格式的规则，而不能继续假装所有结果都是有限非零正规数。",[88,193,195],{"id":194},"再看01与02","再看0.1与0.2",[10,197,198],{},"可以让Python直接报告0.1所存的精确分数：",[17,200,202],{"className":19,"code":201,"language":21,"meta":22,"style":22},"print((0.1).as_integer_ratio())\n# (3602879701896397, 36028797018963968)\n",[24,203,204,216],{"__ignoreMap":22},[27,205,206,208,211,213],{"class":29,"line":30},[27,207,34],{"class":33},[27,209,210],{"class":37},"((",[27,212,41],{"class":33},[27,214,215],{"class":37},").as_integer_ratio())\n",[27,217,218],{"class":29,"line":58},[27,219,220],{"class":54},"# (3602879701896397, 36028797018963968)\n",[10,222,223],{},"分母就是2⁵⁵。这个分数非常接近1/10，却不与它相等。[1] 一个不需要长小数的检验是：若相等，就应有分子乘10等于分母；实际左边比右边多2。",[10,225,226],{},"通常只显示“0.1”，是因为显示程序选择了一个简短、能重新读回同一浮点值的十进制写法。屏幕上的简短文字与底层保存的精确分数，承担着不同角色。",[10,228,229],{},"接着，十进制0.2也先映射为附近的二进制浮点值；加法对这两个已保存的值计算，再把结果舍入回允许的格点。右侧用于比较的0.3，也有自己的输入转换。因此，两边走过的路径并不相同，最终可能落在相邻格点上。",[10,231,232],{},[115,233],{"alt":234,"src":235},"输入的十进制实数先转换为有限二进制格点，保存值相加后再次舍入，最后格式化为十进制输出","/collections-assets/math-beauty/ch35/03-representation-operation-display.png",[10,237,238],{},"图3：输入转换、算术舍入和显示格式是三个不同环节。缩短显示文本，不会倒过来改变先前保存或运算的值。",[10,240,241,242,246],{},"也正因为有这些环节，不能把前面实数证明中的每一步，未经检查就当作浮点操作的逐位保证。一个整数范围内很可靠的等式，跨到精度不够的数量级后，也可能表现不同。例如在上述环境里，float(2",[243,244,245],"strong",{},"53)＋1仍等于float(2","53)：这一带相邻格点已经相差2，增加1落在中点后又被舍回。",[10,248,249],{},"程序没有偷偷把实数1改成0，而是这次相加后的精确结果无法落入当前格式，舍入掩去了改动。迭代停止条件、累计次数与阈值比较，都需要知道自己使用的到底是哪一种数。",[88,251,253],{"id":252},"换一种表示要换得完整","换一种表示，要换得完整",[10,255,256],{},"若任务确实要求按十进制数值进行精确表示，可以明确从十进制字符串构造数值：",[17,258,260],{"className":19,"code":259,"language":21,"meta":22,"style":22},"from decimal import Decimal\nprint(Decimal(\"0.1\") + Decimal(\"0.2\") == Decimal(\"0.3\"))  # True\nprint(Decimal(0.1) == Decimal(\"0.1\"))                  # False\n",[24,261,262,276,315],{"__ignoreMap":22},[27,263,264,267,270,273],{"class":29,"line":30},[27,265,266],{"class":44},"from",[27,268,269],{"class":37}," decimal ",[27,271,272],{"class":44},"import",[27,274,275],{"class":37}," Decimal\n",[27,277,278,280,283,287,290,293,296,299,301,304,306,309,312],{"class":29,"line":58},[27,279,34],{"class":33},[27,281,282],{"class":37},"(Decimal(",[27,284,286],{"class":285},"sU2Wk","\"0.1\"",[27,288,289],{"class":37},") ",[27,291,292],{"class":44},"+",[27,294,295],{"class":37}," Decimal(",[27,297,298],{"class":285},"\"0.2\"",[27,300,289],{"class":37},[27,302,303],{"class":44},"==",[27,305,295],{"class":37},[27,307,308],{"class":285},"\"0.3\"",[27,310,311],{"class":37},"))  ",[27,313,314],{"class":54},"# True\n",[27,316,318,320,322,324,326,328,330,332,335],{"class":29,"line":317},3,[27,319,34],{"class":33},[27,321,282],{"class":37},[27,323,41],{"class":33},[27,325,289],{"class":37},[27,327,303],{"class":44},[27,329,295],{"class":37},[27,331,286],{"class":285},[27,333,334],{"class":37},"))                  ",[27,336,80],{"class":54},[10,338,339],{},"第二行构造函数收到的已经是二进制float近似，它会保留那个值，而不会猜出原来想写的是1/10。[4] 选择新的容器，并不能自动恢复已经丢掉的信息。",[10,341,342],{},"十进制浮点也有精度和范围设置；1/3在十进制里仍然不能有限展开。保存有理数的分子、分母可以保留这类精确关系，但中间整数和计算成本可能增长。表示方式各有适用任务，增加精度也不会把问题本身的不确定性消掉。",[10,344,345],{},"对于本来就带测量误差的数据、数值拟合或模型预测，精确相等未必是合适的验收问题。是否“足够接近”，还要看单位、数量级和容许误差，不能统一塞一个与任务无关的小常数。第38章会把这些条件放回完整的数值结果检查中。",[10,347,348],{},"这一章先解释误差从哪里进入。下一章要进一步问：同样微小的输入变化，为什么有的问题几乎不受影响，有的答案却大幅摆动？那时需要区分机器的表示精度与问题自身的敏感程度。",[88,350,351],{"id":351},"顺手试一下",[10,353,354,357],{},[243,355,356],{},"题目1："," 3/16、7/40、5/8中，哪些能写成有限二进制小数？能的话写出展开，不能的话说明分母中的障碍。“能有限展开”是否保证任意浮点格式都能精确保存？",[10,359,360],{},"**提示：**先约分，看分母是否只含质因子2，再分别检查位数与指数范围。",[10,362,363],{},"**参考答案：**3/16＝0.0011₂，5/8＝0.101₂，都可以有限展开；7/40已约分，分母含5，不能。有限展开仍可能长于格式的有效位数，或超出指数范围，所以不能保证任意格式都能装下。",[10,365,366,369],{},[243,367,368],{},"题目2："," 在3个有效二进制位的教学格式中，考虑[4，8)区间。5.6按最近舍入保存为什么？绝对误差与相对误差各是多少？是否超过本章u＝1/8的界？",[10,371,372],{},"**提示：**这一段的格点是4、5、6、7；相对误差用实际想表示的5.6作分母。",[10,374,375],{},"**参考答案：**5.6离6更近，保存为6。绝对误差为0.4；相对误差为0.4/5.6＝1/14≈0.07143，小于1/8。这只是一次舍入，并未保证后续整个算法的误差也不超过1/8。",[88,377,378],{"id":378},"参考与延伸",[10,380,381,382,389],{},"[1] Python官方教程，",[383,384,388],"a",{"href":385,"rel":386},"https://docs.python.org/3/tutorial/floatingpoint.html",[387],"nofollow","Floating-Point Arithmetic: Issues and Limitations","。核对0.1的binary64表示、精确整数比与显示行为。本文代码在Python 3.12.14的二进制53位环境复核；底层参数可自行检查。",[10,391,392,393,398],{},"[2] David Goldberg，",[383,394,397],{"href":395,"rel":396},"https://docs.oracle.com/cd/E19422-01/819-3693/ncg_goldberg.html",[387],"What Every Computer Scientist Should Know About Floating-Point Arithmetic","，Oracle收录的原论文修订版，D.3节。核对浮点格式、最近舍入与相对误差条件。本文将单位舍入误差记为u，避免与不同资料的epsilon定义混用。",[10,400,401,402,407],{},"[3] Python官方文档，",[383,403,406],{"href":404,"rel":405},"https://docs.python.org/3/library/sys.html#sys.float_info",[387],"sys.float_info","。核对radix、mant_dig与epsilon的具体含义。",[10,409,410,411,416],{},"[4] Python官方文档，",[383,412,415],{"href":413,"rel":414},"https://docs.python.org/3/library/decimal.html",[387],"decimal","。核对从字符串与从float构造十进制数的不同结果及有限精度语义。",[418,419],"hr",{},[10,421,422],{},"作者：Jiancheng Wang · 数学之美（独立原创）",[424,425,426],"style",{},"html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":22,"searchDepth":317,"depth":317,"links":428},[429,430,431,432,433,434,435],{"id":90,"depth":58,"text":90},{"id":127,"depth":58,"text":127},{"id":160,"depth":58,"text":161},{"id":194,"depth":58,"text":195},{"id":252,"depth":58,"text":253},{"id":351,"depth":58,"text":351},{"id":378,"depth":58,"text":378},"md",{"collection":438,"order":439,"slug":440,"author":441},"数学之美",35,"finite-precision","Jiancheng Wang",true,"/column/math-beauty/finite-precision",{"title":5,"description":12},"column/math-beauty/finite-precision","pHLnSaTyjptr0exdyzsnSd7pVoP-YoivkxXc4urFB_I",1791935829214]