考虑以下代码:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些错误?


#1楼

您尝试过胶带解决方案吗?

尝试确定何时发生错误,并使用简短的if语句修复错误,这虽然不是很漂亮,但是对于某些问题,这是唯一的解决方案,这就是其中之一。

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}else { return n * 0.1 + 0.000000000000001 ;}

我在c#的一个科学仿真项目中遇到了同样的问题,我可以告诉你,如果您忽略蝴蝶效应,它将变成一条大胖龙,并在a **中咬你**


#2楼

硬件设计师的观点

我相信我应该为此添加硬件设计师的观点,因为我设计并构建了浮点硬件。 知道错误的来源可能有助于理解软件中发生的事情,并且最终,我希望这可以帮助解释为什么会出现浮点错误并随着时间的推移而累积的原因。

1.概述

从工程的角度来看,大多数浮点运算都将具有一定的错误元素,因为进行浮点计算的硬件仅要求最后的误差小于一个单元的一半。 因此,许多硬件将停止在一个精度上,该精度仅对于单次操作在最后一次产生的误差小于一个单元的一半所必需,而这在浮点除法中尤其成问题。 构成单个操作的要素取决于该单元采用的操作数。 对于大多数情况,它是两个,但是某些单位使用3个或更多操作数。 因此,不能保证重复操作会导致理想的错误,因为随着时间的推移这些错误加起来。

2.标准

大多数处理器遵循IEEE-754标准,但有些使用非规范化或不同的标准。 例如,IEEE-754中存在一种非规范化模式,该模式允许以精度为代价表示非常小的浮点数。 但是,以下内容将涵盖IEEE-754的标准化模式,这是典型的操作模式。

在IEEE-754标准中,只要最后一位小于一个单位的一半,那么硬件设计者就可以允许任何误差/ε值,并且最后的结果必须小于一个单位的一半。一个手术的地方。 这解释了为什么当重复操作时,错误加起来。 对于IEEE-754双精度,这是第54位,因为53位用于表示浮点数的数字部分(规格化),也称为尾数(例如5.3e5中的5.3)。 下一节将更详细地介绍各种浮点操作上的硬件错误原因。

3.除法中舍入错误的原因

浮点除法中错误的主要原因是用于计算商的除法算法。 大多数计算机系统使用乘以逆来计算除法,主要是在Z=X/YZ = X * (1/Y) 。 迭代计算除法,即每个周期计算商的某些位,直到达到所需的精度为止,对于IEEE-754而言,这是任何最后一次误差小于一个单位的东西。 Y(1 / Y)的倒数表在慢除法中称为商选择表(QST),商选择表的位大小通常为基数的宽度,或者为每次迭代中计算出的商,加上一些保护位。 对于IEEE-754标准(双精度(64位)),它将是除法器基数的大小,加上几个保护位k,其中k>=2 。 因此,例如,用于一次计算商2位(基数4)的除法器的典型商选择表将是2+2= 4位(加上一些可选位)。

3.1除法舍入误差:倒数的近似

商选择表中的倒数取决于除法 :慢除法(例如SRT除法)或快速除法(例如Goldschmidt除法); 根据划分算法修改每个条目,以尝试产生尽可能低的错误。 但是,无论如何,所有倒数都是实际倒数的近似值 ,并且会引入一些误差元素。 慢速除法和快速除法方法都是迭代计算商,即每一步都会计算商的位数,然后从被除数中减去结果,然后除法器重复执行这些步骤,直到误差小于二分之一为止。单位放在最后。 慢除法在每个步骤中计算商的位数固定,并且通常构建成本较低,而快速除法在每步中计算可变数位数,并且通常构建成本较高。 除法中最重要的部分是,大多数方法都依赖于近似的倒数重复进行乘法运算,因此容易出错。

4.其他操作中的舍入错误:截断

所有操作中舍入错误的另一个原因是IEEE-754允许的最终答案截断的不同模式。 有截断,向零舍入,最近舍入(默认),向下舍入和向上舍入。 对于单个操作,所有方法最后都会引入误差小于一单位的元素。 随着时间的流逝和重复的操作,截断还会累积地增加所产生的错误。 截断误差在取幂时尤其成问题,涉及某种形式的重复乘法。

5.重复操作

由于执行浮点计算的硬件仅需要产生一个结果,该结果的单个操作的最后一个位置的误差小于一个单元的一半,因此如果不注意,该误差将随着重复的操作而扩大。 这就是为什么在需要有限误差的计算中,数学家使用诸如在 IEEE-754 的最后一位使用四舍五入到最接近的偶数之类的方法的原因,因为随着时间的流逝,误差更可能相互抵消。 间隔算法与IEEE 754舍入模式的变体相结合,以预测舍入误差并进行校正。 由于与其他舍入模式相比其相对误差较低,因此舍入到最接近的偶数位(最后一位)是IEEE-754的默认舍入模式。

请注意,默认的舍入模式( 最后一位舍入到最接近的偶数位)保证一次操作的最后一位的误差小于一个单位的一半。 仅使用截断,向​​上舍入和向下舍入可能会导致错误,最后一个位置的误差大于一个单元的一半,但最后一个位置的误差小于一个单元,因此不建议使用这些模式,除非它们是在间隔算术中使用。

6.总结

简而言之,浮点运算错误的根本原因是硬件的截断和除法时的倒数截断的组合。 由于IEEE-754标准在一次操作中只要求最后一个位置的误差小于一个单元的一半,因此,除非纠正,否则重复操作中的浮点错误将加起来。


#3楼

出现这些怪异的数字是因为计算机出于计算目的使用二进制(基数2)数字系统,而我们使用十进制(基数10)。

大多数小数都不能用二进制或十进制或两种形式精确表示。 结果-舍入(但精确)的数字结果。


#4楼

除了其他正确答案外,您可能还需要考虑缩放值,以避免浮点运算出现问题。

例如:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... 代替:

var result = 0.1 + 0.2;     // result === 0.3 returns false

表达式0.1 + 0.2 === 0.3在JavaScript中返回false ,但是幸运的是,浮点数中的整数运算是精确的,因此可以通过缩放避免十进制表示错误。

作为一个实际的例子,为避免浮点问题,其中精度是最重要的,建议1以整数形式表示货币,该货币代表美分的数量: 2550美分而不是25.50美元。


1 Douglas Crockford: JavaScript:优秀部分 :附录A-糟糕的部分(第105页) 。


#5楼

已经发布了很多很好的答案,但我想再补充一个。

并非所有数字都可以通过浮点数 / 双精度数来表示。例如,在IEEE754浮点标准中,数字“ 0.2”将以单精度表示为“ 0.200000003”。

引擎盖下的实数存储模型将浮点数表示为

即使你可以键入0.2很容易, FLT_RADIXDBL_RADIX为2; 对于使用FPU且使用“ IEEE二进制浮点算术标准(ISO / IEEE Std 754-1985)”的计算机,不是10。

因此,要准确地表示这些数字有些困难。 即使您明确指定此变量,也无需任何中间计算。


#6楼

这里的大多数答案都是用非常干燥的技术术语来解决这个问题。 我想用普通人能理解的术语来解决这个问题。

想象一下,您正在尝试切比萨饼。 你有一个机器人比萨刀,可以削减比萨正好一半。 它可以将整个披萨减半,也可以将现有的薄片减半,但是无论如何,减半总是精确的。

比萨切刀的动作非常精细,如果从整个比萨开始,则将其减半,然后每次将最小的薄片减半,则可以将薄片减半53次 ,直到薄片即使对于其高精度功能而言也太小。 此时,您不能再将这一薄片减半,而必须按原样包含或排除它。

现在,您如何将所有的切片切成这样的厚度,使它们的总和等于比萨饼的十分之一(0.1)或五分之一(0.2)? 真正考虑一下,然后尝试解决。 如果您手边有神话般的精密披萨切割器,您甚至可以尝试使用真正的披萨。 :-)


大多数有经验的程序员,当然知道真正的答案,这是没有办法拼凑出一个确切的十分之一或五分之一的比萨使用这些片,不管你如何精细切片他们。 您可以做一个非常好的近似值,如果您将0.1的近似值与0.2的近似值相加,您会得到一个非常好的0.3的近似值,但是仍然只是一个近似值。

对于双精度数字(可以使您的比萨饼减半53的精度),立即小于或大于0.1的数字是0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。 后者比前者更接近0.1,因此,在输入为0.1的情况下,数值解析器将偏爱后者。

(这两个数字之间的差是我们必须决定包括的“最小切片”,它会引入向上的偏差,而排除的结果是引入向下的偏差。该最小切片的技术术语是ulp 。)

在0.2的情况下,数字都是相同的,只是放大了2倍。同样,我们支持略高于0.2的值。

请注意,在两种情况下,0.1和0.2的近似值都有轻微的向上偏差。 如果我们添加足够的这些偏差,它们将使数字远离我们想要的值,并且实际上,在0.1 + 0.2的情况下,偏差足够大,以致所得的数字不再是最接近的数字到0.3。

特别地,0.1 + 0.2实际上是0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125,而最接近0.3的数字实际上是0.2999999999999999888977697484484957636833191791575。


PS一些编程语言还提供了比萨饼切割器,可以将切成精确的十分之一 。 尽管这种比萨饼切割器并不常见,但是如果您确实有机会使用它,那么当重要的是要精确获得十分之一或五分之一的切片时,您应该使用它。

(最初发布在Quora上。)


#7楼

一些统计数据与此著名的双精度问题有关。

当以0.1(从0.1到100)的步长将所有值( a + b )相加时,我们有〜15%的机会出现精度误差 。 请注意,该错误可能导致值更大或更小。 这里有些例子:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

当以0.1(从100到0.1)的步长减去所有值( a-b ,其中a> b )时,我们有〜34%的机会出现精度误差 。 这里有些例子:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* 15%和34%的确很大,因此在精度至关重要时,请始终使用BigDecimal。 如果使用两位小数(第0.01步),情况会进一步恶化(18%和36%)。


#8楼

我的回答很长,因此我将其分为三个部分。 由于问题是关于浮点数学的,因此我将重点放在机器的实际功能上。 我还专门针对双精度(64位)精度,但是该参数同样适用于任何浮点运算。

前言

IEEE 754双精度二进制浮点格式(binary64)数字表示以下形式的数字

值=(-1)^ s *(1.m 51 m 50 ... m 2 m 1 m 02 * 2 e-1023

64位:

  • 第一比特是符号位 : 1如果数是负的, 0否则为1。
  • 接下来的11位是指数 ,它偏移 1023。换句话说,从双精度数读取指数位后,必须减去1023以获得2的幂。
  • 剩余的52位为有效数字 (或尾数)。 在尾数中,“隐含” 1.总是2,因为任何二进制值的最高有效位是1

1 -IEEE 754支持带符号零的概念- +0-0的区别对待: 1 / (+0)是正无穷大; 1 / (-0)为负无穷大。 对于零值,尾数和指数位均为零。 注意:零值(+0和-0)没有明确地归类为非正规2

2-非正规数不是这种情况, 非正规数的偏移指数为零(隐含0. )。 反规范双精度数的范围是d 分钟 ≤| X | ≤d MAX,其中d 分钟 (最小可表示非零数)为2 -1023 - 51(≈4.94 * 10 -324)和d MAX(最大反规范数,其尾数完全由1 s)为2 - 1023 + 1-2 -1023-51 (≈2.225 * 10 -308 )。


将双精度数转换为二进制

存在许多在线转换器,可以将双精度浮点数转换为二进制(例如,在binaryconvert.com上 ),但是这里有一些示例C#代码,用于获取双精度浮点数的IEEE 754表示形式(我用冒号将三个部分分开( : ):

public static string BinaryRepresentation(double value)
{long valueInLongType = BitConverter.DoubleToInt64Bits(value);string bits = Convert.ToString(valueInLongType, 2);string leadingZeros = new string('0', 64 - bits.Length);string binaryRepresentation = leadingZeros + bits;string sign = binaryRepresentation[0].ToString();string exponent = binaryRepresentation.Substring(1, 11);string mantissa = binaryRepresentation.Substring(12);return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

切入点:原始问题

(跳到底部的TL; DR版本)

Cato Johnston (提问者)问为什么0.1 + 0.2!= 0.3。

IEEE 754以二进制形式(用冒号分隔三个部分)表示,其值表示为:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

请注意,尾数由0011的重复数字组成。 这是为什么计算会出错的关键 -0.1、0.2和0.3不能以有限数量的二进制位精确地以二进制表示,超过1 / 9、1 / 3或1/7可以精确地表示为十进制数字

还要注意,我们可以将指数的幂减小52,并将二进制表示形式的点向右移动52个位置(非常类似于10 -3 * 1.23 == 10 -5 * 123)。 然后,这使我们能够将二进制表示形式表示为它以a * 2 p形式表示的精确值。 其中“ a”是整数。

将指数转换为十进制,除去偏移,然后重新添加隐含的1 (在方括号中),0.1和0.2是:

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

要相加两个数字,指数必须相同,即:

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

由于总和不是2 n * 1. {bbb}的形式,因此我们将指数增加1,然后将小数点( 二进制 )移至:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

现在尾数中有53位(第53位在上一行的方括号中)。 IEEE 754的默认舍入模式为“ 最接近 舍入 ”-即,如果数字x介于两个值ab之间 ,则选择最低有效位为零的值。

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875= 2^-2  * 1.0011001100110011001100110011001100110011001100110011x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

请注意, ab仅在最后一位不同。 ...0011 + 1 = ...0100 在这种情况下,最低有效位为零的值为b ,因此总和为:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

而0.3的二进制表示形式是:

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011=  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

这仅与0.1和0.2之和的二进制表示形式相差2 -54

0.1和0.2的二进制表示形式是IEEE 754允许的最准确的数字表示形式。由于默认的舍入模式,将这些表示形式相加会导致仅在最低有效位上有所不同的值。

TL; DR

用IEEE 754二进制表示形式(用冒号分隔三个部分)编写0.1 + 0.2 ,并将其与0.3进行比较,这就是(我将不同的位放在方括号中):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

转换回十进制,这些值为:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

差异恰好是2 -54 ,约为5.5511151231258×10 -17-与原始值相比并不重要(对于许多应用程序)。

比较浮点数的最后几位本质上是危险的,因为任何读过著名的“ 每个计算机科学家应该了解的浮点算术 ”(涵盖了该答案的所有主要部分)的人都将知道。

大多数计算器使用附加的保护位来解决此问题,这就是0.1 + 0.2给出0.3 :最后几位是四舍五入的。


#9楼

鉴于没有人提及此事...

一些高级语言(例如Python和Java)附带了克服二进制浮点限制的工具。 例如:

  • Python的decimal模块和Java的BigDecimal类 ,它们内部用十进制表示法表示数字(与二进制表示法相对)。 两者的精度都有限,因此它们仍然容易出错,但是它们使用二进制浮点算法解决了最常见的问题。

    处理货币时,小数非常好:十美分加二十美分总是正好是三十美分:

     >>> 0.1 + 0.2 == 0.3 False >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3') True 

    Python的decimal模块基于IEEE标准854-1987 。

  • Python的fractions模块和Apache Common的BigFraction类 。 两者都将有理数表示为(numerator, denominator)对,并且它们给出的结果比十进制浮点算术更准确。

这些解决方案都不是完美的(特别是如果我们考虑性能,或者需要非常高的精度),但是它们仍然使用二进制浮点算法解决了许多问题。


#10楼

可以在数字计算机中实现的那种浮点数学运算必须使用实数的近似值及其上的运算。 ( 标准版本运行多达五十页的文档,并设有一个委员会来处理其勘误表并作进一步完善。)

这种近似是不同类型近似的混合,由于其与精确度的特定偏离方式,每种近似都可以忽略不计或仔细考虑。 它还在硬件和软件级别上涉及许多明显的例外情况,大多数人会假装不注意而已。

如果需要无限精度(例如,使用数字π代替许多更短的替代之一),则应编写或使用符号数学程序。

但是,如果您对有时浮点数学的值模糊并且逻辑和错误会迅速累积的想法感到满意,并且可以编写要求和测试以允许这样做,那么您的代码就可以经常使用其中的内容您的FPU。


#11楼

这个问题的许多重复项中有许多都询问浮点舍入对特定数字的影响。 在实践中,通过查看感兴趣的计算的确切结果,而不是仅仅阅读它,会更容易感觉到它是如何工作的。 某些语言提供了实现此目的的方法-例如在Java中将floatdouble转换为BigDecimal

由于这是一个与语言无关的问题,因此需要与语言无关的工具,例如Decimal to Floating-Point Converter 。

将其应用于问题中的数字,视为双精度:

0.1转换为0.1000000000000000055511151231257827021181583404541015625,

0.2转换为0.200000000000000011102230246251565404236316680908203125,

0.3转换为0.299999999999999988897769753748434595763683319091796875和

0.30000000000000004转换为0.3000000000000000444089209850062616169452667236328125。

手动或使用十进制计算器(例如Full Precision Calculator)将前两个数字相加,将显示实际输入的确切总和为0.3000000000000000166533453693793781063544750213623046875。

如果将其舍入为0.3的等效值,则舍入误差将为0.0000000000000000277555756156289135105907917022705078125。 舍入到0.30000000000000004的等效值还会产生舍入误差0.0000000000000000277555756156289135105907917022705078125。 从头到尾的平局决胜局适用。

返回到浮点转换器,0.30000000000000004的原始十六进制为3fd3333333333334,该数字以偶数结尾,因此是正确的结果。


#12楼

否,不折断,但大多数十进制小数必须近似

摘要

不幸的 ,浮点运算精确的,与我们通常的以10为底的数字表示形式不匹配,因此事实证明,我们经常给它输入的内容与我们编写的内容略有出入。

即使简单的数字(如0.01、0.02、0.03、0.04 ... 0.24)也不能精确表示为二进制分数。 如果您将0.01,.02,.03 ...相加,则直到达到0.25时,您才能获得以2为底的可表示的第一部分。 如果您尝试使用FP,则0.01的值可能会略有偏差,因此将25个值相加达到精确的0.25的唯一方法将需要一长串因果关系,其中包括保护位和舍入。 很难预测,因此我们举起手来说“ FP是不精确的”,但这不是真的。

我们不断地给FP硬件一些东西,这些东西在base 10中似乎很简单,但是在base 2中却是重复的部分。

这怎么发生的?

当我们用小数写时,每个分数(特别是每个终止小数)都是形式的有理数

一个/(2 n x 5 m

用二进制,我们只得到2 n项,即:

一/ 2 n

因此,在小数,我们不能代表三分之一 。 因为以10为底的素数包括2作为素数,所以我们可以写为二进制分数的每个数字可以以10为底的分数。 但是,几乎所有我们以10为基数的分数都无法用二进制表示。 在0.01、0.02、0.03 ... 0.99的范围内,我们的FP格式只能表示三个数字:0.25、0.50和0.75,因为它们都是1 / 4、1 / 2和3/4,所有数字仅使用2 n项的素数因子。

在基体10也不能代表三分之一 。 但是,在二进制,我们不能做1月10日 三分之一

因此,虽然每个二进制分数都可以用十进制表示,但事实并非如此。 实际上,大多数十进制小数都以二进制重复。

处理它

通常会指示开发人员进行<epsilon比较,更好的建议可能是四舍五入为整数值(在C库中:round()和roundf(),即保持FP格式),然后进行比较。 舍入到特定的小数部分长度可以解决大多数输出​​问题。

同样,在实数运算问题(FP是在早期的,价格昂贵的早期计算机上发明的问题)上,宇宙的物理常数和所有其他度量值仅由相对较少的有效数字知道,因此整个问题空间无论如何都是“ inexact”。 FP“准确性”在这种应用程序中不是问题。

当人们尝试使用FP进行豆计数时,确实会出现整个问题。 它确实可以做到这一点,但前提是您坚持使用整数值,这会破坏使用它的目的。 这就是为什么我们拥有所有这些十进制分数软件库的原因。

我喜欢Chris的Pizza回答,因为它描述了实际的问题,而不仅仅是描述“不准确性”的惯常做法。 如果FP只是“不准确”,我们可以解决这个问题 ,几十年前就可以做到。 我们之所以没有这样做,是因为FP格式紧凑,快速,并且是处理大量数字的最佳方法。 而且,这是航天时代和军备竞赛以及早期尝试通过使用小型内存系统的非常慢的计算机解决重大问题的尝试所遗留下来的。 (有时,用于1位存储的单个磁芯 ,但这是另一回事了。 )

结论

如果您只是在银行里数豆,那么首先使用十进制字符串表示形式的软件解决方案就可以很好地工作。 但是您不能那样做量子色动力学或空气动力学。


#13楼

存储在计算机中的浮点数由两部分组成:一个整数和一个以整数为底并乘以该整数的指数。

如果计算机是在基座10的工作, 0.1 。将1 x 10⁻¹0.2将是2 x 10⁻¹ ,和0.3将是3 x 10⁻¹ 。 整数数学既简单又精确,因此添加0.1 + 0.2显然会得出0.3

计算机通常不以10为基数工作,而是以2为基数。您仍然可以获得某些值的精确结果,例如0.51 x 2⁻¹0.251 x 2⁻² ,并将它们的结果加到3 x 2⁻²0.75 。 究竟。

问题在于数字可以精确地以10为底,而不能以2为底。这些数字需要四舍五入到最接近的等值。 假定非常常见的IEEE 64位浮点格式,最接近0.1数字是3602879701896397 x 2⁻⁵⁵ ,最接近0.2数字是7205759403792794 x 2⁻⁵⁵ ; 将它们加在一起将得到10808639105689191 x 2⁻⁵⁵或精确的十进制值0.3000000000000000444089209850062616169452667236328125 。 浮点数通常会四舍五入以显示。


#14楼

我可以补充吗? 人们总是认为这是计算机问题,但是如果您用手计数(以10为基数),则除非获得无穷大的0.333,否则无法获得(1/3+1/3=2/3)=true 。 ..到0.333 ...就像基数2中的(1/10+2/10)!==3/10问题一样,您将其截断为0.333 + 0.333 = 0.666并可能将其舍入为0.667,这将是在技​​术上也不准确。

以三进制计数,但是三分之二不是问题-也许每只手上用15根手指进行比赛会问为什么十进制数学运算被破坏了...


#15楼

只是为了好玩,我按照标准C99的定义玩了浮点数的表示法,并编写了以下代码。

该代码在3个独立的组中打印浮点数的二进制表示形式

SIGN EXPONENT FRACTION

然后打印出一个总和,当以足够的精度求和时,它将显示硬件中实际存在的值。

因此,当您编写float x = 999... ,编译器将以函数xx打印的位表示形式转换该数字,以使函数yy打印的总和等于给定的数字。

实际上,这个和只是一个近似值。 对于数字999,999,999,编译器将在浮点数的位表示中插入数字1,000,000,000

在代码之后,我附加了一个控制台会话,在该会话中,我计算了硬件中确实存在的两个常量(减去PI和999999999)的条件之和,并由编译器插入其中。

#include <stdio.h>
#include <limits.h>void
xx(float *x)
{unsigned char i = sizeof(*x)*CHAR_BIT-1;do {switch (i) {case 31:printf("sign:");break;case 30:printf("exponent:");break;case 23:printf("fraction:");break;}char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;printf("%d ", b);} while (i--);printf("\n");
}void
yy(float a)
{int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));int fraction = ((1<<23)-1)&(*(int*)&a);int exponent = (255&((*(int*)&a)>>23))-127;printf(sign?"positive" " ( 1+":"negative" " ( 1+");unsigned int i = 1<<22;unsigned int j = 1;do {char b=(fraction&i)!=0;b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);} while (j++, i>>=1);printf("*2^%d", exponent);printf("\n");
}void
main()
{float x=-3.14;float y=999999999;printf("%lu\n", sizeof(x));xx(&x);xx(&y);yy(x);yy(y);
}

这是一个控制台会话,在其中计算硬件中存在的float的实际值。 我用bc打印主程序输出的项的总和。 可以在python repl或类似的东西中插入该和。

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

而已。 该值999999999实际上是

999999999.999999446351872

您也可以用bc检查-3.14是否也受到干扰。 不要忘记在bc设置scale因子。

显示的总和就是硬件内部的总和。 通过计算获得的值取决于您设置的比例。 我确实将scale因子设置为15。在数学上,它具有无限的精度,似乎是1,000,000,000。


#16楼

看待这种情况的另一种方式:使用64位来表示数字。 因此,最多可以精确表示2 ** 64 = 18,446,744,073,709,551,616个不同的数字。

但是,Math表示,0和1之间已经存在无数个小数。IEE754定义了一种编码,可以有效地使用这64位来处理更大的数字空间以及NaN和+/- Infinity,因此用数字填充的准确表示的数字之间存在间隙仅近似数字。

不幸的是0.3处于差距。


#17楼

由于该线程在当前浮点实现的一般讨论中有所分支,因此我补充说,有一些项目正在解决其问题。

以https://posithub.org/为例,该示例展示了一个称为posit(及其前身unum)的数字类型,该类型有望以更少的位数提供更高的准确性。 如果我的理解是正确的,那么它也可以解决问题中的这类问题。 非常有趣的项目,其背后的人是约翰·古斯塔夫森博士(John Gustafson )的数学家。 整个过程都是开源的,在C / C ++,Python,Julia和C#( https://hastlayer.com/arithmetics )中有许多实际实现。


#18楼

Math.sum (javascript)....一种替换运算符

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001

Object.defineProperties(Math, {sign: {value: function (x) {return x ? x < 0 ? -1 : 1 : 0;}},precision: {value: function (value, precision, type) {var v = parseFloat(value), p = Math.max(precision, 0) || 0, t = type || 'round';return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);}},scientific_to_num: {  // this is from https://gist.github.com/jiggzsonvalue: function (num) {//if the number is in scientific notation remove itif (/e/i.test(num)) {var zero = '0',parts = String(num).toLowerCase().split('e'), //split into coeff and exponente = parts.pop(), //store the exponential partl = Math.abs(e), //get the number of zerossign = e / l,coeff_array = parts[0].split('.');if (sign === -1) {num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');} else {var dec = coeff_array[1];if (dec)l = l - dec.length;num = coeff_array.join('') + new Array(l + 1).join(zero);}}return num;}}get_precision: {value: function (number) {var arr = Math.scientific_to_num((number + "")).split(".");return arr[1] ? arr[1].length : 0;}},diff:{value: function(A,B){var prec = this.max(this.get_precision(A),this.get_precision(B));return +this.precision(A-B,prec);}},sum: {value: function () {var prec = 0, sum = 0;for (var i = 0; i < arguments.length; i++) {prec = this.max(prec, this.get_precision(arguments[i]));sum += +arguments[i]; // force float to convert strings to number}return Math.precision(sum, prec);}}
});

这个想法是使用数学运算符来避免浮点错误

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

还请注意,Math.diff和Math.sum自动检测要使用的精度

Math.sum接受任意数量的参数


#19楼

一个不同的问题已被命名为此重复项:

在C ++中,为什么cout << x的结果与调试器为x显示的值不同?

问题中的xfloat变量。

一个例子是

float x = 9.9F;

调试器显示9.89999962cout操作的输出为9.9

答案是coutfloat缺省精度为6,因此将其舍入为6个十进制数字。

请参阅此处以供参考


#20楼

为了提供最佳解决方案,我可以说我发现了以下方法:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

让我解释一下为什么这是最好的解决方案。 就像上面提到的其他答案一样,最好使用可立即使用的Javascript toFixed()函数来解决问题。 但是您很可能会遇到一些问题。

假设您要将两个浮点数相加,例如0.20.7这里是: 0.2 + 0.7 = 0.8999999999999999

您的预期结果是0.9这意味着在这种情况下您需要1位数精度的结果。 因此,您应该使用(0.2 + 0.7).tofixed(1)但不能仅给toFixed()一个参数,因为它取决于给定的数字,例如

`0.22 + 0.7 = 0.9199999999999999`

在此示例中,您需要2位数的精度,所以它应该是toFixed(2) ,那么适合每个给定浮点数的参数应该是什么?

您可能会说在每种情况下都设为10:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

该死的! 您将如何处理9之后的那些不需要的零? 现在是时候将其转换为浮点以使其如您所愿了:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

现在,您已经找到了解决方案,最好将其作为如下功能提供:

function floatify(number){return parseFloat((number).toFixed(10));}

让我们自己尝试一下:

 function floatify(number){ return parseFloat((number).toFixed(10)); } function addUp(){ var number1 = +$("#number1").val(); var number2 = +$("#number2").val(); var unexpectedResult = number1 + number2; var expectedResult = floatify(number1 + number2); $("#unexpectedResult").text(unexpectedResult); $("#expectedResult").text(expectedResult); } addUp(); 
 input{ width: 50px; } #expectedResult{ color: green; } #unexpectedResult{ color: red; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> + <input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> = <p>Expected Result: <span id="expectedResult"></span></p> <p>Unexpected Result: <span id="unexpectedResult"></span></p> 

您可以通过以下方式使用它:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

正如W3SCHOOLS提出的还有另一种解决方案一样,您可以乘以除以解决上述问题:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

请记住,尽管看上去(0.2 + 0.1) * 10 / 10根本不起作用! 我更喜欢第一个解决方案,因为我可以将其用作将输入浮点数转换为准确的输出浮点数的函数。


#21楼

从Python 3.5开始,您可以使用math.isclose()函数测试近似相等性:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False

#22楼

这实际上是作为该问题的答案- 我将这个答案汇总在一起时,已作为问题的重复部分关闭了,所以现在我不能在此处发布...因此我将其发布在这里!


问题摘要:

在工作表10^-8/100010^-11评估为“ 相等”,而在VBA中则没有。

在工作表上,数字默认为科学计数法。

如果将单元格更改为具有15小数点的Number的数字格式( Ctrl + 1 ),则会得到:

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984

因此,它们绝对不相同...一个大约为零,另一个大约为1。

Excel并非旨在处理极小的数字-至少不是针对库存安装。 有加载项可帮助提高数字精度。


Excel是根据IEEE二进制浮点算术标准( IEEE 754 )设计的。 该标准定义了如何存储和计算浮点数 。 IEEE 754标准被广泛使用,因为它允许将浮点数存储在合理的空间中,并且计算可以相对快速地进行。

浮点数比定点表示法的优势在于它可以支持更大范围的值。 例如,具有与位于第三位小数点后5位十进制数字可以表示数字的定点表示123.3412.232.45 ,等等而具有5位精度浮点表示形式可以表示1.2345,12345,0.00012345类似地,浮点表示法还允许在宽范围内进行计算,同时保持精度。 例如,


其他参考:

  • 办公室支持: 以科学(指数)表示法显示数字
  • Microsoft 365博客: 了解浮点精度 ,又称“ Excel为什么给我看似错误的答案?”
  • Office支持: 在Excel中设置舍入精度
  • 办公室支持: POWER功能
  • SuperUser: 我可以在Excel VBA变量中存储的最大值(数字)是多少?

#23楼

想象一下,以10为底的精度以8位数字工作。 您检查是否

1/3 + 2 / 3 == 1

并得知这返回false 。 为什么? 好吧,作为实数

1/3 = 0.333 ....2/3 = 0.666 ....

截断到小数点后八位,我们得到

0.33333333 + 0.66666666 = 0.99999999

当然,这与1.00000000相差0.00000001


具有固定位数的二进制数的情况完全类似。 作为实数,我们有

1/10 = 0.0001100110011001100 ...(以2为基)

1/5 = 0.0011001100110011001 ...(以2为底)

如果我们将它们截断为7位,那么我们将得到

0.0001100 + 0.0011001 = 0.0100101

另一方面,

3/10 = 0.01001100110011 ...(以2为底)

被截断为7位的是0.0100110 ,并且它们之间相差0.0000001


确切的情况稍微有些微妙,因为这些数字通常以科学计数法存储。 因此,例如,我们可以将其存储为指数和尾数,而不是将1/10存储为0.0001100 ,而可以将其存储为1.10011 * 2^-4类的东西。 这会影响您为计算获得的精度位数。

结果是由于这些舍入误差,您实际上根本不想在浮点数上使用==。 而是可以检查它们的差的绝对值是否小于某个固定的小数。


#24楼

十进制数如0.10.2 ,以及0.3没有精确表示以二进制编码的浮点类型。 0.10.2的近似值之和不同于0.3的近似值,因此0.1 + 0.2 == 0.3的虚假性在这里可以更清楚地看出:

#include <stdio.h>int main() {printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");printf("0.1 is %.23f\n", 0.1);printf("0.2 is %.23f\n", 0.2);printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);printf("0.3 is %.23f\n", 0.3);printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));return 0;
}

输出:

0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17

为了更可靠地评估这些计算,您需要对浮点值使用基于十进制的表示形式。 C标准默认不指定此类类型,而是作为技术报告中描述的扩展。

_Decimal32_Decimal64_Decimal128类型可能在您的系统上可用(例如, GCC在选定的目标上支持它们,但Clang在OS X上不支持它们)。


#25楼

实际上很简单。 当您拥有以10为底的系统(如我们的系统)时,它只能表示使用底的素数的分数。 10的素数是2和5。因此,由于分母都使用10的素数,所以1 / 2、1 / 4、1 / 5、1 / 8和1/10都可以清楚地表达。 / 3、1 / 6和1/7都是重复的小数,因为它们的分母使用3或7的质数。在二进制(或基数2)中,唯一的质数是2。因此,您只能清楚地表达小数仅包含2作为主要因子。 以二进制形式,1 / 2、1 / 4、1 / 8都将干净地表示为小数。 而1/​​5或1/10将重复小数。 因此,在以10为基数的系统中使用干净的小数时,0.1和0.2(1/10和1/5)在计算机正在运行的以2为基数的系统中重复小数。当对这些重复的小数进行数学运算时,最终会剩下余数当您将计算机的2进制数(二进制)转换为更易读的10进制数时,这些值会保留下来。

来自https://0.30000000000000004.com/


#26楼

我刚刚看到了围绕浮点数的这个有趣的问题:

考虑以下结果:

error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1

我们可以清楚地看到2**53+1时的断点-一切正常,直到2**53为止。

>>> (2**53) - int(float(2**53))
0

发生这种情况的原因是双精度二进制:IEEE 754双精度二进制浮点格式:binary64

从Wikipedia页面获取双精度浮点格式 :

双精度二进制浮点是PC上常用的格式,尽管其性能和带宽成本较高,但其范围比单精度浮点宽。 与单精度浮点格式一样,与相同大小的整数格式相比,它在整数上缺乏精度。 通常简称为double。 IEEE 754标准将binary64指定为具有:

  • 符号位:1位
  • 指数:11位
  • 精确度:53位(显式存储52位)

给定的64位双精度基准,给定的偏置指数和52位小数所假定的实数值为

要么

感谢@a_guest向我指出这一点。


#27楼

二进制浮点数学就是这样。 在大多数编程语言中,它基于IEEE 754标准 。 JavaScript使用64位浮点表示形式,与Java的double相同。 问题的症结在于数字以这种格式表示为整数乘以2的幂。 分母不是2的幂的有理数(例如0.1 ,即1/10 )无法精确表示。

对于标准binary64格式的0.1 ,表示形式可以完全按照

  • 0.1000000000000000055511151231257827021181583404541015625 (十进制)
  • 0x1.999999999999ap-4 十六进制表示法中的 0x1.999999999999ap-4

相反,有理数0.1 ,即1/10 ,可以完全写为

  • 十进制的0.1 ,或者
  • 0x1.99999999999999...p-4 ,类似于C99十六进制表示法,其中...表示9的无休止序列。

程序中的常数0.20.3也将接近其真实值。 碰巧的是,最接近0.2 double 0.2数大于有理数0.2但最接近0.3 double 0.3却小于有理数0.30.10.2之和最终大于有理数0.3 ,因此与代码中的常数不一致。

每个计算机科学家都应该对浮点算术了解什么是对浮点算术问题的相当全面的处理。 有关更容易理解的说明,请参见floating-point-gui.de 。

旁注:所有位置(以N为底的)数字系统均会精确共享此问题

普通的旧十进制数(以10为底)有相同的问题,这就是为什么像1/3这样的数字最终会变成0.333333333 ...

您偶然发现了一个数字(3/10),该数字很容易用十进制表示,但不适合二进制。 它也是双向的(在某种程度上):1/16是一个丑陋的数字,十进制(0.0625),但是在二进制中,它看起来像10,000十进制(0.0001)**一样整洁-如果我们在习惯于在我们的日常生活中使用基数2的数字系统,您甚至会查看该数字,并本能地理解将某物减半,一次又一次减半可以到达那里。

**当然,这并不完全是将浮点数存储在内存中的方式(它们使用科学计数形式)。 但是,它确实说明了二进制浮点精度误差趋于增加的观点,因为我们通常感兴趣的“真实世界”数通常是10的幂-但这仅仅是因为我们使用了十进制数天-今天。 这也是为什么我们要说71%而不是“每7个中有5个”的原因(71%是一个近似值,因为5/7不能用任何十进制数字精确表示)。

否:二进制浮点数没有被破坏,它们恰好与其他所有基数N的系统一样不完美:)

侧面说明:在编程中使用浮点数

实际上,这种精度问题意味着您需要使用舍入函数将浮点数四舍五入为您感兴趣的任意小数位,然后再显示它们。

您还需要用允许一定程度的公差的比较替换相等性测试,这意味着:

不要if (float1 == float2) { ... }

而是执行if (Math.Abs(float1 - float2) < myToleranceValue) { ... }

需要为您的特定应用选择myToleranceValue-这与您准备允许多少“摆动空间”以及要比较的最大数字有很大关系(由于精度下降)问题)。 当心所选语言(JavaScript中的Number.EPSILON)的“ double.Epsilon”样式常量。 这些不得用作公差值。


#28楼

浮点舍入错误。 由于缺少素数5,所以0.1在base-2中不能像在base-10中一样准确地表示。正如1/3可以用无数个数字来表示十进制一样,而在base-3中则是“ 0.1”, 0.1在base-2中采用无数位数,而在base-10中则采用无数位数。 而且计算机没有无限的内存量。


#29楼

浮点舍入错误。 从每位计算机科学家应该了解的浮点算法中 :

将无限多个实数压缩为有限数量的位需要近似表示。 尽管有无限多个整数,但是在大多数程序中,整数计算的结果可以存储在32位中。 相反,在给定固定位数的情况下,大多数使用实数的计算将产生无法使用那么多位数精确表示的数量。 因此,浮点计算的结果通常必须四舍五入,以使其适合其有限表示形式。 舍入误差是浮点计算的特征。


#30楼

当您将.1或1/10转换为以2为基数(二进制)时,会在小数点后得到一个重复模式,就像试图以10为基数表示1/3。该值不精确,因此您不能执行使用普通的浮点方法进行精确数学运算。


#31楼

我的解决方法:

function add(a, b, precision) {var x = Math.pow(10, precision || 2);return (Math.round(a * x) + Math.round(b * x)) / x;
}

精度是指在加法过程中要保留的小数点后要保留的位数。

浮点数学运算是否被破坏?相关推荐

  1. [Google Guava] 12-数学运算

    原文链接 译文链接 译者:沈义扬 范例 1 int logFloor = LongMath.log2(n, FLOOR); 2 int mustNotOverflow = IntMath.checke ...

  2. 计算机系统基础实验 - 同符号浮点数加法运算/无符号定点数乘法运算的机器级表示

    实验3 同符号浮点数加法运算/无符号定点数乘法运算的机器级表示 实验序号:3 实验名称:同符号浮点数加法运算/无符号定点数乘法运算的机器级表示 适用专业:软件工程 学 时 数:2学时 一.实验目的 1 ...

  3. 定点数和浮点数加减乘除运算详解【计算机组成原理】---真的建议收藏啊!!!

    前言: 你知道计算机内部是如何进行加减运算的吗?可能你知道,那你知道计算机内部是如何进行乘除法运算的呢?肯定和我们十进制运算是不一样的.当我查找资料的时候,发现除了书本很少有这样的知识点.所以我想和大 ...

  4. 关于不能够精确的对浮点数进行运算的问题

    http://edu.eoe.cn/   在线课堂 昨天看到一篇帖子说了几个很明显的简单的浮点的运算,计算机都会算错. 我引过来给大家看看:' 运行代码: System.out.println(0.0 ...

  5. 浮点数相加php,利用php怎么实现一个浮点数精确运算功能

    利用php怎么实现一个浮点数精确运算功能 发布时间:2020-12-15 16:06:03 来源:亿速云 阅读:94 作者:Leah 今天就跟大家聊聊有关利用php怎么实现一个浮点数精确运算功能,可能 ...

  6. 24、Power Query-数学运算的应用(统计男女人数)

    本节重点:Power Query-数学运算的应用(统计男女人数) 数学运算功能位置: 这一块应用起来比较简单,用处不是特别大,这里仅作简单说明. 例子:下图展示的人员表是姓名+身份证号码,现在要统计出 ...

  7. 计组之数据运算:11、浮点数的运算

    11.浮点数的运算 思维导图 科学技术法的运算 浮点数的运算 舍入问题 浮点数的强制类型转化 思维导图 科学技术法的运算 浮点数的运算 舍入问题 浮点数的强制类型转化 int->float: i ...

  8. 【Python】解决浮点数间运算存在不确定尾数的问题

    #浮点数间运算存在不确定尾数,所以会输出False if 0.1+0.2==0.3:print("Ture\n") else:print("False\n") ...

  9. c语言浮点数如何精确计算,浮点数精确运算的分析和解决办法

    1.01 + 2.01 = 3.02 2.01 * 2.01 = 4 0401   不知你注意没有,这个很寻常的等式,你如果将它放在C++中,Java中,Basic中,它 居然是不成立的.计算机在开玩 ...

最新文章

  1. 认识实时动态测量技术
  2. hellocharts-android开源图表库(效果非常好)
  3. 在Java8的foreach()中不能break,如果需要continue时,可以使用return
  4. spoj Favorite Dice(概率dp+期望)
  5. 2021牛客暑期多校训练营7 F-xay loves trees(线段树+滑动窗口)
  6. 面向对象思想精华总结
  7. github流程图_逆天插件,VSCode里也能画流程图了?Visio可以淘汰了?
  8. Oracle中字符串转义问题总结
  9. 初学Python01
  10. Centos7安装官方JDK
  11. Unity基础学习之Unity引擎学习(一)
  12. 解决问题:vscode中文乱码(亲测有效)
  13. UE4编辑器下Tick的实现
  14. oracle sequence nextval,SOS!!! Sequence.nextval 的问题
  15. 数据分析师的就业薪资
  16. 读书印记 - 《星船伞兵》
  17. tablewidget
  18. (二)51单片机基础——LED
  19. 西门子plc怎样实现远程调试、远程上下载程序?
  20. Bilibili到底有多少御坂妹?(二)

热门文章

  1. zabbix源码安装
  2. Js获取file上传控件的文件路径总结
  3. windows下ch340 usb转串口芯片的驱动从哪里下载?
  4. VS2010测试方面的文章
  5. 网络设备常用排障命令——Juniper vyatta 大河SDN
  6. 升级Linux下的sudo
  7. Mysql平滑迁移(重构后的数据平滑迁移)
  8. 解决zabbix图形乱码
  9. VBS 打开图片-幻灯片形式
  10. 自己的mySql用户