浮点数学是否破碎?

时间:2009-02-25 21:39:02

标签: math language-agnostic floating-point floating-accuracy

考虑以下代码:

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

为什么会出现这些不准确之处?

32 个答案:

答案 0 :(得分:1978)

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

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

编写
  • 0.1000000000000000055511151231257827021181583404541015625十进制或
  • {li> 0x1.999999999999ap-4C99 hexfloat notation

相反,有理数0.1,即1/10,可以完全按照

编写
  • 0.1十进制或
  • 0x1.99999999999999...p-4与C99 hexfloat符号类似,其中...代表9的无限序列。

程序中的常量0.20.3也将与其真实值近似。碰巧最接近的double0.2大于有理数0.2,但最接近的double0.3小于有理数0.3 1}}。 0.10.2的总和大于有理数0.3,因此不同意代码中的常量。

对浮点算术问题的一个相当全面的处理是What Every Computer Scientist Should Know About Floating-Point Arithmetic。有关更易于理解的说明,请参阅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不能用任何十进制数精确表示)。

所以没有:二进制浮点数没有被破坏,它们碰巧与其他所有base-N数系统一样不完美:)

侧面注意:在编程中使用Floats

实际上,这个精度问题意味着您需要使用舍入函数将浮点数舍入到显示它们之前感兴趣的多个小数位。

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

执行if (float1 == float2) { ... }

取而代之的是if (Math.Abs(float1 - float2) < myToleranceValue) { ... }

myToleranceValue可以是1/2 ^ 16(0.0000152587890625)。在Javascript中,提供值Number.EPSILON供您用作容差。

答案 1 :(得分:555)

硬件设计师的观点

我相信自从我设计和构建浮点硬件以来,我应该为此添加一个硬件设计师的视角。了解错误的起源可能有助于理解软件中发生的事情,最终,我希望这有助于解释为什么浮点错误发生并且似乎随着时间的推移而累积的原因。

1。概述

从工程角度来看,大多数浮点运算都会有一些错误因素,因为执行浮点计算的硬件只需要在最后一个位置的误差小于一个单位的一半。因此,对于单个操作而言,许多硬件将停留在仅为了产生小于一个单元的一个单元的错误所需的精度,这在浮点时尤其成问题师。单个操作的构成取决于单元占用的操作数。对于大多数情况,它是两个,但有些单位需要3个或更多操作数。因此,无法保证重复操作会导致所需的错误,因为错误会随着时间的推移而增加。

2。标准

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

在IEEE-754标准中,只要硬件设计者不到最后一个单元的一半,就允许任何错误/ epsilon值,结果只需要小于1一次操作最后一个单元的一半。这解释了为什么当重复操作时,错误加起来。对于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分区舍入误差:倒数近似

商选择表中的倒数取决于division method:慢速划分,如SRT划分,或快速划分,如Goldschmidt划分;根据除法算法修改每个条目,以试图产生尽可能低的错误。但是,在任何情况下,所有倒数都是实际倒数的近似值,并引入了一些误差元素。慢速划分和快速划分方法都迭代地计算商,即每一步计算商的一些位数,然后从被除数中减去结果,并且除法器重复这些步骤直到误差小于一半最后一个单位。慢速划分方法在每个步骤中计算商的固定位数,并且通常构建成本较低,并且快速划分方法计算每步的可变位数并且通常构建成本更高。除法方法中最重要的部分是它们中的大多数都依赖于倒数的近似的重复乘法,因此它们容易出错。

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

所有操作中舍入错误的另一个原因是IEEE-754允许的最终答案的截断模式不同。有截断,向零舍入,round-to-nearest (default),向下舍入和向上舍入。对于单个操作,所有方法在最后位置引入少于一个单元的误差元素。随着时间的推移和重复的操作,截断也会累积地增加结果误差。这种截断误差在求幂中尤其成问题,它涉及某种形式的重复乘法。

5。重复操作

由于执行浮点计算的硬件只需要产生一个结果,错误小于单个操作的最后一个单位的一半,如果没有观察,错误将在重复操作上增加。这就是在需要有界误差的计算中,数学家使用诸如使用最接近的even digit in the last place IEEE-754之类的方法的原因,因为随着时间的推移,错误更可能相互抵消和Interval Arithmetic结合IEEE 754 rounding modes的变体来预测舍入误差,并更正它们。由于与其他舍入模式相比其相对误差较小,因此舍入到最接近的偶数位(在最后一位)是IEEE-754的默认舍入模式。

请注意,默认舍入模式(舍入到最接近的even digit in the last place)可确保一次操作的最后一个位置的误差小于一个单位的一半。单独使用截断,向​​上舍入和向下舍入可能导致错误大于最后一个位置的一个单位的一半,但在最后一个位置小于一个单位,因此不建议使用这些模式,除非它们是用于区间算术。

6。摘要

简而言之,浮点运算中出错的根本原因是硬件截断和截断情况下倒数的截断。由于IEEE-754标准在单个操作中仅需要在最后一个位置中小于一个单元的一半的误差,因此除非经过校正,否则重复操作的浮点误差将相加。

答案 2 :(得分:418)

当您将.1或1/10转换为基数2(二进制)时,您会在小数点后得到重复模式,就像尝试在基数10中表示1/3一样。该值不准确,因此您可以用正常的浮点方法用它做精确的数学运算。

答案 3 :(得分:271)

这里的大多数答案都是以非常干燥的技术术语来解决这个问题。我希望以正常人能够理解的方式解决这个问题。

想象一下,你正试图切开比萨饼。你有一个机器人披萨刀,可以将披萨片完全切成两半。它可以将整个披萨减半,或者它可以将现有切片减半,但无论如何,减半总是精确的。

披萨切割器具有非常精细的动作,如果你从整个披萨开始,然后将其减半,并且每次继续将最小切片减半,则可以在切片前减半 53次太小,甚至它的高精度能力。此时,您不能再将那个非常薄的切片减半,但必须按原样包含或排除它。

现在,你将如何将所有切片分成几乎十分之一(0.1)或五分之一(0.2)的披萨?真的想一想,试试吧。如果您手边有神话般的精密披萨刀,您甚至可以尝试使用真正的披萨。 : - )


当然,大多数有经验的程序员都知道真正的答案,即无论切片多么精细,都无法使用这些切片将完全十分之一或五分之一的披萨拼凑在一起他们。你可以做一个非常好的近似,如果你用近似值0.2加上0.1的近似值,你会得到0.3的近似值,但它仍然只是近似值。

对于双精度数字(这是允许您将披萨减半的精度53次),立即减去和大于0.1的数字是0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。后者比前者更接近0.1,因此如果输入为0.1,则数字解析器将支持后者。

(这两个数字之间的差异是&#34;最小的切片&#34;我们必须决定包括,这会引入向上偏差,或排除,这会引入向下偏差。这个最小的技术术语切片是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.299999999999999988897769753748434595763683319091796875。


P.S。一些编程语言还提供可以split slices into exact tenths的披萨切割器。虽然这种披萨切割器并不常见,但是如果您确实可以使用它,那么在能够获得切片的十分之一或五分之一时,您应该使用它。

(Originally posted on Quora.)

答案 4 :(得分:203)

浮点舍入错误。由于缺少素数因子5,0.1不能在base-2中准确地表示为基数为10.正如1/3采用无穷多位数来表示十进制,但在base-3中为“0.1”, 0.1在base-2中占用无数个数字,而不是在base-10中。计算机没有无限的内存。

答案 5 :(得分:112)

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

例如:

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: The Good Parts: Appendix A - Awful Parts (page 105)

答案 6 :(得分:52)

存储在计算机中的浮点数由两部分组成,一个整数和一个指数,基数被取并乘以整数部分。

如果计算机在10号基地工作,则0.11 x 10⁻¹0.22 x 10⁻¹0.33 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。浮点数通常是四舍五入的。

答案 7 :(得分:43)

浮点舍入错误。来自What Every Computer Scientist Should Know About Floating-Point Arithmetic

  

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

答案 8 :(得分:30)

我的解决方法:

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

precision 是指在添加时小数点后要保留的位数。

答案 9 :(得分:27)

已发布了许多好的答案,但我想再追加一个。

并非所有数字都可以通过花车 / 双打来表示 例如,数字&#34; 0.2&#34;将表示为&#34; 0.200000003&#34; IEEE754浮点标准中的单精度。

引擎盖下的商店实数模型表示浮点数为

enter image description here

即使您可以轻松输入0.2FLT_RADIXDBL_RADIX也是2;对于使用&#34; IEEE二进制浮点运算标准(ISO / IEEE标准754-1985)&#34;的计算机而言,不是10;

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

答案 10 :(得分:25)

一些与这个着名的双精度问题相关的统计数据。

当使用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&gt; 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。使用2位十进制数字(步骤0.01),情况会恶化一些(18%和36%)。

答案 11 :(得分:24)

不,没有破坏,但大多数小数必须近似

  

摘要

浮点运算 是精确的,不幸的是,它与我们通常的基数为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硬件提供基本10中看似简单的东西,但它是基数2中的重复部分。

  

这是怎么发生的?

当我们用十进制写时,每个分数(具体地,每个终止小数)是形式的有理数

a /(2 n x 5 m

在二进制文件中,我们只得到 2 n 一词,即:

a / 2 n

所以在十进制中,我们不能代表 1 / 3 。因为基数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 项。

在base 10 中,我们不能代表 1 / 3 。但在二进制中,我们不能做 1 / 10 1 / 3

因此,虽然每个二进制分数都可以用十进制写,但反之则不然。事实上,大多数小数部分以二进制重复。

  

处理它

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

此外,关于实际数字运算问题(FP是早期,可怕的昂贵计算机上发明的问题),宇宙的物理常数和所有其他测量仅为相对较少的有效数字所知,所以无论如何,整个问题空间都是“不精确的”。 FP“准确度”在这种应用中不是问题。

当人们尝试使用FP进行bean计数时,整个问题就出现了。它确实适用于此,但只有当你坚持使用积分值时才会这样做,哪种方式会失败。 这就是我们拥有所有小数部分软件库的原因。

我喜欢Chris的披萨答案,因为它描述的是实际问题,而不仅仅是关于“不准确”的常见问题。如果FP只是“不准确”,我们可以修复,并且几十年前就已经完成了。我们之所以没有这个原因,是因为FP格式紧凑而且速度快,而且它是压缩大量数字的最佳方式。此外,它是太空时代和军备竞赛的遗产,也是使用小型内存系统解决大型问题的早期尝试。 (有时,单个磁芯用于1位存储,但那是another story.

  

结论

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

答案 12 :(得分:18)

您是否尝试过胶带解决方案?

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

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

我在c#的科学模拟项目中遇到了同样的问题,我可以告诉你,如果你忽略了蝴蝶效应,它会变成一条巨大的肥龙并在a **中咬你。

答案 13 :(得分:15)

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

大多数小数都无法用二进制或十进制或两者精确表示。结果 - 舍入(但精确)数字结果。

答案 14 :(得分:13)

我可以添加;人们总是认为这是一个计算机问题,但如果你用手计数(基数为10),你就无法得到(1/3+1/3=2/3)=true,除非你有无穷大增加0.333 ......到0.333 ...所以就像对于基数2中的(1/10+2/10)!==3/10问题,您将其截断为0.333 + 0.333 = 0.666,并可能将其四舍五入为0.667,这在技术上也是不准确的。

三进制数,但三分之一不是问题 - 也许每个手上有15个手指的比赛会问为什么你的十进制数学被打破了......

答案 15 :(得分:13)

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

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似乎完全不起作用! 我更喜欢第一种解决方案,因为我可以将其用作将输入浮点数转换为准确的输出浮点数的函数。

答案 16 :(得分:12)

鉴于没有人提到过这个......

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

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

    处理金钱时小数点非常好:十美分外加二十美分总是三十美分:

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

    Python的decimal模块基于IEEE standard 854-1987

  • Python的fractions module和Apache Common的BigFraction class。两者都将有理数表示为(numerator, denominator)对,它们可以提供比十进制浮点算法更准确的结果。

这些解决方案都不是完美的(特别是如果我们看一下性能,或者我们需要非常高的精度),但它们仍然解决了二进制浮点运算的大量问题。

答案 17 :(得分:9)

可以在数字计算机中实现的浮点数学类型必然使用实数和操作的近似值。 (标准版本运行超过五十页的文档,并有一个委员会来处理其勘误和进一步改进。)

这种近似是不同类型的近似的混合,由于其与精确度的偏差的特定方式,每种近似可以被忽略或仔细考虑。它还涉及硬件和软件级别的一些明显的例外情况,大多数人在假装不注意的情况下走过去。

如果您需要无限精度(例如,使用数字π而不是其中许多较短的替身),您应该编写或使用符号数学程序。

但是,如果您认为有时浮点数学在值和逻辑上是模糊的并且错误可以快速累积,并且您可以编写您的需求和测试以允许这样做,那么您的代码可以经常得到你的FPU中有什么。

答案 18 :(得分:8)

只是为了好玩,我按照标准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);
}

这是一个控制台会话,我在其中计算硬件中存在的浮点的实际值。我使用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是否也受到干扰。不要忘记在scale中设置bc因子。

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

答案 19 :(得分:5)

另一种看待这种情况的方法:使用64位来表示数字。因此,无法准确表示超过2 ** 64 = 18,446,744,073,709,551,616个不同的数字。

然而,Math表示在0和1之间已经存在无限多的小数.IEE 754定义了一种编码,可以有效地使用这些64位用于更大的数字空间加上NaN和+/-无穷大,因此在准确表示之间存在差距填充数字的数字只是近似的。

不幸的是,0.3存在差距。

答案 20 :(得分:4)

由于这个线程在当前的浮点实现上进行了一般性的讨论,我想补充说有一些项目可以解决它们的问题。

https://posithub.org/为例,它展示了一个名为posit(及其前身unum)的数字类型,它承诺以更少的位提供更好的精度。如果我的理解是正确的,它也解决了问题中的问题。非常有趣的项目,背后的人是数学家Dr. John Gustafson。整个过程都是开源的,在C / C ++,Python,Julia和C#(https://hastlayer.com/arithmetics)中有很多实际的实现。

答案 21 :(得分:3)

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

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

答案 22 :(得分:3)

想象一下,以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


由于这些数字通常以科学计数法存储,因此实际情况略微微妙。因此,例如,根据我们为指数和尾数分配的位数,我们可以将其存储为0.0001100而不是1.10011 * 2^-4来存储。这会影响您的计算精度。

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

答案 23 :(得分:2)

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/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = 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而不是运算符来避免浮动错误

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

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

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

答案 24 :(得分:2)

另一个问题已被命名为此问题的副本:

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

问题中的xfloat变量。

一个例子是

float x = 9.9F;

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

答案是cout float的默认精度为6,因此它会舍入到6位小数。

请参阅here以获取参考资料

答案 25 :(得分:2)

正常算术以10为底,因此小数代表十进制,百分数等。当您尝试以二进制base-2算术表示浮点数时,您要处理的一半,四分之一,八分之一等。

在硬件中,浮点存储为整数尾数和指数。尾数代表有效数字。指数就像科学记数法,但是它使用2而不是10的底数。例如,64.0将以1的尾数表示,指数为6。0.125将以1的尾数和-3指数表示。

浮点小数必须加2的负幂

0.1b = 0.5d
0.01b = 0.25d
0.001b = 0.125d
0.0001b = 0.0625d
0.00001b = 0.03125d

以此类推。

在处理浮点算术时,通常使用错误增量而不是使用相等运算符。代替

if(a==b) ...

您将使用

delta = 0.0001; // or some arbitrarily small amount
if(a - b > -delta && a - b < delta) ...

答案 26 :(得分:1)

这实际上是作为this question的答案-与我正在整理的这个问题 的重复这个答案,所以现在我不能在这里发布...所以我将在这里发布!


  

问题摘要:

     

在工作表10^-8/100010^-11上,其评估结果为等于,而在VBA中则没有。

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

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

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

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

Excel并非旨在处理极端的少量数字-至少不适用于库存安装。有一些插件可以帮助提高数字精度。


  

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

     

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

img


其他参考文献:

答案 27 :(得分:1)

  

实际上非常简单。当您拥有以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/

答案 28 :(得分:1)

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

考虑以下结果:

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

Enter image description here

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

Double-precision floating-point format的维基百科页面上:

  

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

     
      
  • 符号位:1位
  •   
  • 指数:11位
  •   
  • 极高的精度:53位(显式存储了52位)
  •   
     

Enter image description here

     

由给定的64位双精度数据(具有给定的偏置指数和52位的分数)假定的实际值是

     

Enter image description here

     

     

Enter image description here

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

答案 29 :(得分:1)

在硬件级别,浮点数表示为二进制数的分数(以2为底)。例如,小数部分:

0.125

的值为1/10 + 2/100 + 5/1000,并且以同样的方式为二进制分数:

0.001

的值为0/2 + 0/4 + 1/8。这两个分数具有相同的值,唯一的区别是第一个是小数,第二个是二进制。

不幸的是,大多数十进制分数不能以二进制分数精确表示。因此,通常,您给出的浮点数仅近似于要存储在机器中的二进制分数。

以10为底的问题更容易解决。例如,分数1/3。您可以将其近似为小数:

0.3

或更好,

0.33

或更好,

0.333

等不管您写多少小数位,结果都永远不会精确到1/3,但它总是一个接近的估计值。

同样,无论您使用多少个以2为基数的小数位,十进制值0.1都不能精确表示为二进制分数。在基数2中,1/10是以下周期数:

0.0001100110011001100110011001100110011001100110011 ...

停止任意数量的位,您将获得一个近似值。

对于Python,在典型的计算机上,浮点数的精度使用53位,因此输入十进制0.1时存储的值是二进制分数。

0.00011001100110011001100110011001100110011001100110011010

接近但不完全等于1/10。

由于解释器中浮点数的显示方式,很容易忘记存储的值是原始十进制分数的近似值。 Python仅显示二进制存储值的十进制近似值。如果Python要输出存储为0.1的二进制近似值的真实十进制值,它将输出:

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

这比大多数人期望的小数位数要多得多,因此Python显示舍入的值以提高可读性:

>>> 0.1
0.1

重要的是要理解,实际上这是一种错觉:存储的值不完全是1/10,只是在显示屏上四舍五入了存储值。使用这些值执行算术运算后,这一点就会变得明显:

>>> 0.1 + 0.2
0.30000000000000004

这种行为是机器浮点表示的本质所固有的:它不是Python中的错误,也不是代码中的错误。您可以在使用硬件支持来计算浮点数的所有其他语言中观察到相同类型的行为(尽管某些语言默认情况下不会使差异可见,或者在所有显示模式下都不可见)。

这是另一个内在的惊喜。例如,如果您尝试将值2.675舍入到小数点后两位,您将得到

>>> round (2.675, 2)
2.67

round()原语的文档指出,它会四舍五入为最接近零的值。由于小数部分恰好位于2.67和2.68之间的一半,因此您应该期望得到2.68(二进制近似值)。但是,情况并非如此,因为当十进制小数2.675转换为浮点数时,它以近似值存储,其精确值为:

2.67499999999999982236431605997495353221893310546875

由于近似值比2.68略微接近2.67,因此舍入向下。

如果您将小数点后半部分舍入很重要,则应使用小数模块。顺便说一句,十进制模块还提供了一种方便的方式来“查看”为任何浮点数存储的确切值。

>>> from decimal import Decimal
>>> Decimal (2.675)
>>> Decimal ('2.67499999999999982236431605997495353221893310546875')

0.1不能精确存储在1/10中的另一个结果是0.1的十个值的总和也不等于1.0:

>>> sum = 0.0
>>> for i in range (10):
... sum + = 0.1
...>>> sum
0.9999999999999999

二进制浮点数的算法有很多这样的惊喜。 “ 0.1”的问题将在下面的“表示错误”部分中详细说明。有关此类惊奇的更完整列表,请参见“浮点危险”。

确实没有简单的答案,但是不要对浮动的virtula数字过于怀疑!在Python中,浮点数运算中的错误是由于底层硬件引起的,并且在大多数计算机上,每个运算的错误不超过2 ** 53分之1。对于大多数任务来说,这是多余的,但是您应该记住,这些操作不是十进制运算,并且对浮点数的每个运算都可能会遇到新的错误。

尽管存在病理情况,但对于大多数常见用例,只需将其四舍五入到所需的小数位数就可以得到预期的结果。为了更好地控制浮点数的显示方式,请参阅字符串格式语法,以了解str.format()方法的格式规范。

答案的这一部分详细说明了“ 0.1”的示例,并说明了如何自行对此类案件进行准确的分析。我们假设您熟悉浮点数的二进制表示形式。术语表示误差表示大多数十进制小数不能完全用二进制表示。这是Python(或Perl,C,C ++,Java,Fortran和其他许多语言)通常不以十进制显示确切结果的主要原因:

>>> 0.1 + 0.2
0.30000000000000004

为什么? 1/10和2/10不能以二进制分数精确表示。但是,今天(2010年7月)的所有机器都遵循IEEE-754标准来计算浮点数。并且大多数平台都使用“ IEEE-754双精度”来表示Python浮点数。双精度IEEE-754使用53位精度,因此在阅读计算机时,尝试将0.1转换为J / 2 ** N形式的最接近的分数,其中J为正好是53位的整数。重写:

1/10 ~ = J / (2 ** N)

在:

J ~ = 2 ** N / 10

请记住,J恰好是53位(所以> = 2 ** 52但<2 ** 53),N的最佳值是56:

>>> 2 ** 52
4503599627370496
>>> 2 ** 53
9007199254740992
>>> 2 ** 56/10
7205759403792793

所以56是N的唯一可能值,而J恰好剩下53位。因此,J的最佳可能值是四舍五入的商:

>>> q, r = divmod (2 ** 56, 10)
>>> r
6

由于进位大于10的一半,因此通过四舍五入获得最佳近似值:

>>> q + 1
7205759403792794

因此,“ IEEE-754双精度”中1/10的最佳近似值是2 ** 56以上,即:

7205759403792794/72057594037927936

请注意,由于向上舍入,因此结果实际上略大于1/10;如果我们不进行四舍五入,则商将略小于1/10。但这绝对不是1/10!

因此计算机永远不会“看到” 1/10:它看到的是上面给出的精确分数,这是使用“ IEEE-754”中的双精度浮点数的最佳近似值:

>>>. 1 * 2 ** 56
7205759403792794.0

如果将这个分数乘以10 ** 30,我们可以观察到它的30个小数位的权重值。

>>> 7205759403792794 * 10 ** 30 // 2 ** 56
100000000000000005551115123125L

表示存储在计算机中的精确值大约等于十进制值0.100000000000000005551115123125。在Python 2.7和Python 3.1之前的版本中,Python将这些值四舍五入到17个有效的小数位,显示为``0.10000000000000001''。在当前版本的Python中,显示的值是其分数要尽可能短的值,同时转换回二进制文件时给出的表示形式完全相同,只需显示“ 0.1”即可。

答案 30 :(得分:0)

诸如0.10.20.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标准默认不指定此类类型,而是作为Technical Report中描述的扩展名。类型_Decimal32_Decimal64_Decimal128在您的系统上可能可用(例如gccselected targets上支持它们,但是clang不支持在OS / X上)。

答案 31 :(得分:-1)

2020 TypeScript答案:

原因:

https://0.30000000000000004.com

库:

I built an NPM library for these reasons, but here's the code. You can either import the library or copy the code below.

// TypeScript Type: Math Type
type MathType = 'Add' | 'Subtract' | 'Multiply' | 'Divide';

// Math Exact
export const mathExact = (mathType: MathType, numberOne:number , numberTwo: number) => {
  // Decimal Places
  let numberOneDecimalPlaces: number = 0;
  let numberTwoDecimalPlaces: number = 0;

  // Float: Number One
  if (numberOne.toString().indexOf('.') !== -1) {
    // Assign Decimal Places
    numberOneDecimalPlaces = numberOne.toString().length - 1 - numberOne.toString().indexOf('.');
  }

  // Float: Number Two
  if (numberTwo.toString().indexOf('.') !== -1) {
    // Assign Decimal Places
    numberTwoDecimalPlaces = numberTwo.toString().length - 1 - numberTwo.toString().indexOf('.');
  }

  // Decimal Places: Equal
  if (numberOneDecimalPlaces === numberTwoDecimalPlaces) {
    // Integers (Off By Decimal Places)
    const numberOneInteger: number = numberOne * Math.pow(10, numberOneDecimalPlaces);
    const numberTwoInteger: number = numberTwo * Math.pow(10, numberTwoDecimalPlaces);

    // Math Type: Add
    if (mathType === 'Add') {
      // Integer Total
      const integerTotal: number = numberOneInteger + numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces);
    }
    // Math Type: Subtract
    else if (mathType === 'Subtract') {
      // Integer Total
      const integerTotal: number = numberOneInteger - numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces);
    }
    // Math Type: Multiply
    else if (mathType === 'Multiply') {
      // Integer Total
      const integerTotal: number = numberOneInteger * numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces + numberTwoDecimalPlaces);
    }
    // Math Type: Divide
    else if (mathType === 'Divide') {
      return numberOneInteger / numberTwoInteger;
    }
  }
  // Decimal Places: Number One Has More
  else if (numberOneDecimalPlaces > numberTwoDecimalPlaces) {
    // Integers (Off By Decimal Places)
    const numberOneInteger: number = numberOne * Math.pow(10, numberOneDecimalPlaces);
    const numberTwoInteger: number = numberTwo * Math.pow(10, numberOneDecimalPlaces);

    // Math Type: Add
    if (mathType === 'Add') {
      // Integer Total
      const integerTotal: number = numberOneInteger + numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces);
    }
    // Math Type: Subtract
    else if (mathType === 'Subtract') {
      // Integer Total
      const integerTotal: number = numberOneInteger - numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces);
    }
    // Math Type: Multiply
    else if (mathType === 'Multiply') {
      // Integer Total
      const integerTotal: number = numberOneInteger * numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces + numberTwoDecimalPlaces + (numberOneDecimalPlaces - numberTwoDecimalPlaces));
    }
    // Math Type: Divide
    else if (mathType === 'Divide') {
      return numberOneInteger / numberTwoInteger;
    }
  }
  // Decimal Places: Number Two Has More
  else if (numberOneDecimalPlaces < numberTwoDecimalPlaces) {
    // Integers (Off By Decimal Places)
    const numberOneInteger: number = numberOne * Math.pow(10, numberTwoDecimalPlaces);
    const numberTwoInteger: number = numberTwo * Math.pow(10, numberTwoDecimalPlaces);

    // Math Type: Add
    if (mathType === 'Add') {
      // Integer Total
      const integerTotal: number = numberOneInteger + numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberTwoDecimalPlaces);
    }
    // Math Type: Subtract
    else if (mathType === 'Subtract') {
      // Integer Total
      const integerTotal: number = numberOneInteger - numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberTwoDecimalPlaces);
    }
    // Math Type: Multiply
    else if (mathType === 'Multiply') {
      // Integer Total
      const integerTotal: number = numberOneInteger * numberTwoInteger;

      // Decimal Total (Convert Back X Amount Of Decimal Places)
      return integerTotal / Math.pow(10, numberOneDecimalPlaces + numberTwoDecimalPlaces + (numberTwoDecimalPlaces - numberOneDecimalPlaces));
    }
    // Math Type: Divide
    else if (mathType === 'Divide') {
      return numberOneInteger / numberTwoInteger;
    }
  }
};

添加:

mathExact('Add', 1, 2); // 3
mathExact('Add', .1, .2);  // .3
mathExact('Add', 1.123, .2);  // 1.323
mathExact('Add', .02, 1.123); // 1.143

减:

mathExact('Subtract', 1, 2): // -1
mathExact('Subtract', .1, .2);  // -.1
mathExact('Subtract', 1.123, .2);  // .923
mathExact('Subtract', .02, 1.123); // -1.103

相乘:

mathExact('Multiply', 1, 2); // 2
mathExact('Multiply', .1, .2);  // .02
mathExact('Multiply', 1.123, .2);  // .2246
mathExact('Multiply', .02, 1.123); // .002246

划分:

mathExact('Divide', 1, 2); // .5
mathExact('Divide', .1, .2);  // .5
mathExact('Divide', 1.123, .2);  // 5.615
mathExact('Divide', .02, 1.123); // .017809439002671415