编程语言可以采取什么措施来避免浮点陷阱?


28

对浮点算术及其缺点的误解是编程中令人吃惊和困惑的主要原因(请考虑与Stack Overflow有关的“数字未正确添加”的问题数)。考虑到许多程序员尚未了解其含义,因此它有可能引入许多细微的错误(尤其是在财务软件中)。有什么可以编程语言做,以避免其对那些不熟悉的概念陷阱,同时还提供了高速时的精度是不是为那些关键的明白的概念?


26
编程语言唯一可以避免浮点处理陷阱的事情就是禁止它。请注意,这还包括以10为基数的浮点数,除了金融应用程序已预先适应于此之外,它通常也不准确。
David Thornley

4
这就是“数值分析”的目的。了解如何最大程度地减少精度损失-又名浮点陷阱。

浮点问题的一个很好的例子:stackoverflow.com/questions/10303762/0-0-0-0-0
奥斯汀·亨利

Answers:


47

您说“特别是用于财务软件”,这使我心烦意乱:金钱不是浮标,而是整数

当然,它看起来像个漂浮物。它在那里有一个小数点。但这仅仅是因为您已经习惯了使问题混淆的部门。金钱总是整数。在美国,是美分。(在某些情况下,我认为可能是米尔斯,但现在暂时忽略它。)

因此,当您说$ 1.23时,实际上是123美分。总是,总是,总是用这些术语来做数学,你会没事的。有关更多信息,请参见:

直接回答这个问题,编程语言应该只包含Money类型作为合理的原语。

更新

好吧,我应该只说“总是”两次,而不是三遍。金钱的确是一个整数。那些有其他想法的人可以尝试寄给我0.3美分,并在您的银行对帐单上显示结果。但是正如评论员指出的那样,当您需要对类似货币的数字进行浮点运算时,很少有例外。例如,某些种类的价格或利息计算。即使那样,也应该将其视为例外。金钱以整数形式进出,因此您的系统越接近这个数字,它就会越聪明。


20
@JoelFan:您误会了特定于平台的实现的概念。
whatsisname 2011年

12
这不是那么简单。除其他外,利息计算的确会产生小数分,并且必须根据指定的方法在某个点取整。
凯文·克莱恩

24
虚构的-1,因为我缺少降职代表:) ...这可能对您的钱包里的东西都是正确的,但在许多会计情况下,您很可能只需要处理十分之一美分或更少的零头。Decimal是处理此问题的唯一理智的系统,您的评论
暂时

9
@kevin cline:计算中包含小数分,但是关于如何处理它们有约定。财务计算的目标不是数学上的正确性,而是获得与使用计算器的银行家完全相同的结果。
David Thornley

6
通过用“理性”代替“整数”一词,一切都将是完美的
Emilio Garavaglia

15

在许多情况下,为十进制类型提供支持会有所帮助。许多语言都有十进制类型,但使用率不高。

了解使用实数表示时发生的近似值很重要。同时使用十进制和浮点类型9 * (1/9) != 1是正确的声明。当常量时,优化器可以优化计算,使其正确。

提供近似运算符会有所帮助。但是,这样的比较是有问题的。请注意,.9999万亿美元大约等于1万亿美元。您能把差额存入我的银行帐户吗?


2
0.9999...一万亿美元实际上等于一万亿美元。
我的正确观点

5
@JUST:是的,但是我还没有遇到任何带有将要保存的寄存器的计算机0.99999...。它们都在某个时间截断,导致不平等。 0.9999对于工程来说已经足够了。出于财务目的,并非如此。
BillThor

2
但是,什么样的系统使用万亿美元作为基础单位而不是美元呢?
布拉德

@Brad尝试在您的计算器上计算(1万亿/ 3)* 3。您得到什么价值?
BillThor 2012年

8

当我上大学时,我们被告知在计算机科学的第一年(二年级)演讲中要做的事情(该课程也是大多数科学课程的前提条件)

我回想起一位讲师的话:“浮点数是近似值。为钱使用整数类型。对BCD编号使用FORTRAN或其他语言进行精确计算。” (然后,他指出了近似值,使用的经典例子0.2不可能在二进制浮点数中准确表示)。那个星期在实验室练习中也发现了这一点。

同一讲课:“如果必须从浮点数中获得更高的准确性,请对术语进行排序。将小数字加在一起,而不是大数字。” 那记在我的脑海。

几年前,我有一些球面几何形状,需要非常精确且仍然快速。PC机上的80位double并没有减少它,因此我在程序中添加了一些类型,以便在执行交换操作之前对术语进行排序。问题解决了。

在抱怨吉他的质量之前,请先学习演奏。

四年前,我有一个为JPL工作的同事。他对我们在某些事情上使用FORTRAN表示怀疑。(我们需要离线计算的超精确数值模拟。)“我们用C ++取代了所有FORTRAN”,他自豪地说道。我不再怀疑他们为什么错过了一颗行星。


2
+1适用于合适工作的合适工具。虽然我实际上并不使用FORTRAN。值得庆幸的是,我也没有在工作中使用我们的财务系统。
James Khoury 2012年

“如果必须从浮点数中获得更高的准确性,请对术语进行排序。将小数字加在一起,而不是大数字。” 有样品吗?
mamcx 2014年

@mamcx想象一个十进制浮点数,只包含一位数字。每个中间结果取整时,1.0 + 0.1 + ... + 0.1将返回计算(重复10次)1.0。做它的其他方式回合,你得到的中间结果0.20.3......,1.0终于2.0。这是一个极端的示例,但是对于现实的浮点数,也会发生类似的问题。基本思想是,将大小相近的数字相加会导致最小的误差。从最小的数字开始,因为它们的总和更大,因此更适合于添加更大的数字。
maaartinus

不过,Fortran和C ++中的浮点数几乎是相同的。两者都是准确且离线的,而且我很确定Fortran没有本机BCD真实内容
马克

8

警告:浮点类型System.Double缺乏直接相等测试的精度。

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

我不相信在语言层面上可以或应该做的任何事情。


1
我已经很长时间没有使用浮子了,所以我很好奇。这是实际存在的编译器警告,还是您只想看到的警告?
Karl Bielefeldt

1
@Karl-就我个人而言,我还没有看到它或不需要它,但我想它可能会对敬业但环保的开发人员有用。
ChaosPandion 2011年

1
二进制浮点类型在质量上不比Decimal进行相等性测试时好或坏。之间的差异1.0m/7.0m*7.0m1.0m可能幅度小于之间的区别的许多订单1.0/7.0*7.0,但它不是零。
2012年

1
@Patrick-我不确定你在说什么。在一种情况下为真与所有情况下都为真之间存在巨大差异。
ChaosPandion

1
@ChaosPandion这篇文章中的示例的问题不是相等比较,而是浮点文字。没有浮点数的精确值为1.0 / 10。使用尾数内的整数进行计算时,浮点数学运算可得出100%准确的结果。
Patrick

7

默认情况下,语言应对非整数使用任意精度有理数。

那些需要优化的人可以总是要求浮动。在C和其他系统编程语言中将它们用作默认值是有意义的,但在当今流行的大多数语言中却没有。


1
那你如何处理非理性数字?
dsimcha 2011年

3
使用浮点数的方法与之相同:近似。
Waquo

1
我必须说,我认为这很有意义,大多数需要精确数字的人都需要理性而不是非理性(科学和工程可以使用非理性,但是您又回到了近似领域,或者您正在做一些非常专业的纯数学)
jk。

1
与具有硬件支持的计算相比,使用任意精度有理数进行的计算通常会慢几个数量级(可能慢许多个数量级)double。如果计算需要精确到百万分之几,那么花一微秒的时间来精确到十亿分之几最好,而不是绝对精确地花费一秒。
2012年

5
@supercat:您的建议只是过早优化的后代。当前的情况是,绝大多数程序员根本不需要快速数学,然后被难以理解的浮点(误)行为所困扰,因此需要快速数学的程序员相对较少,却没有键入一个额外的字符。在七十年代这是有道理的,现在只是胡说八道。默认值应该是安全的。那些需要斋戒的人应该要求它。
Waquo

4

涉及浮点数的两个最大问题是:

  • 应用于计算的单位不一致(请注意,这也以相同方式影响整数算术)
  • 无法理解FP数是一个近似值以及如何智能地处理舍入。

第一种故障只能通过提供包括值和单位信息的复合类型来纠正。例如,一个lengtharea包含单位的值(分别为米或平方米或英尺和平方英尺)。否则,您必须勤于始终使用一种类型的度量单位,并且仅当我们与人类共享答案时才转换为另一种。

第二类失败是概念上的失败。当人们认为失败是失败时,失败就显现出来了绝对数字。它影响相等性运算,累积舍入误差等。例如,对于一个系统,两个测量在一定误差范围内是等效的可能是正确的。即,当您不关心小于+/- .1。的差异时,.999和1.001与1.0大致相同。但是,并非所有系统都如此宽松。

如果需要任何语言级别的功能,那么我将其称为等精度。在NUnit,JUnit和类似构造的测试框架中,您可以控制被认为正确的精度。例如:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

例如,如果对C#或Java进行了更改以包括一个精度运算符,则它可能看起来像这样:

if(.999 == 1.001 within .1) { /* do something */ }

但是,如果提供这样的功能,则还必须考虑+/-面不同时相等性良好的情况。例如,+ 1 / -10将认为两个数字相等,如果其中一个数字比第一个数字多1个或小于10个。要处理这种情况,您可能还需要添加一个range关键字:

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }

2
我要换顺序。概念问题无处不在。相比较而言,单位转换问题相对较小。
S.Lott

我喜欢精确运算符的概念,但是正如您进一步提到的那样,绝对需要深思熟虑。就我个人而言,我会更倾向于将其视为自己完整的语法结构。
ChaosPandion 2011年

这也可以很容易地在图书馆中完成。
Michael K

1
@ dan04:我在考虑“所有计算都精确到百分之一以内”之类的问题时考虑得更多。我已经看过焦油坑,它是度量处理的单位,并且我已经远离了。
TMN

1
大约25年前,我看到了一个数字包,其中包含由一对浮点数组成的类型,这些浮点数代表一个数量的最大和最小可能值。随着数字通过计算,最大值和最小值之间的差异将会增大。有效地,这提供了一种方法来知道计算值中存在多少实际精度。
超级猫

3

编程语言可以做什么?不知道这个问题是否有一个答案,因为编译器/解释器代表程序员进行的使他/她的生活变得更轻松的任何事情通常都会影响性能,清晰度和可读性。我认为C ++方式(只为需要的东西付费)和Perl方式(不出意外的原则)都有效,但这取决于应用程序。

程序员仍然需要使用这种语言并了解它如何处理浮点数,因为如果不这样做,他们将做出假设,并且有一天所规定的行为将与他们的假设不符。

我对程序员需要知道的事情的看法:

  • 系统上和语言中可用的浮点类型
  • 需要什么类型
  • 如何表达代码中需要哪种类型的意图
  • 如何正确利用任何自动类型提升来平衡清晰度和效率,同时保持正确性

3

编程语言可以做些什么来避免[浮点]陷阱...?

使用合理的默认值,例如内置的对decmials的支持。

Groovy可以很好地做到这一点,尽管您可以付出一些努力仍然可以编写代码来引入浮点不精确度。


3

我同意在语言级别上没有任何事情要做。程序员必须了解计算机是离散的且受限制的,并且计算机中表示的许多数学概念仅是近似值。

没关系,浮点数。必须了解一半的位模式用于负数,而2 ^ 64实际上很小,可以避免整数算术的典型问题。


不同意的是,目前大多数语言都对二进制浮点类型提供了过多的支持(为什么甚至为浮点定义了==?)而对有理数或十进制数却没有足够的支持
jk。

@jk:即使永远不能保证任何计算的结果都等于其他任何计算的结果,对于将相同的值分配给两个变量的情况,相等比较仍然很有用(尽管也许通常实现的相等规则太宽松了,因为x== y并不意味着对进行计算x将产生与对进行相同计算的结果相同的结果y
2012年

@supercat您仍然需要比较,但是我宁愿语言要求我为每个浮点比较指定一个公差,然后我仍然可以通过选择公差= 0来回到相等,但是我至少被迫这样做选择
jk。

3

语言可以做的一件事-从浮点类型中删除相等性比较,而不是直接与NAN值进行比较。

相等性测试仅存在于采用两个值和一个增量的函数调用中,或者对于诸如C#这样的允许类型具有使用其他值和该增量的EqualsTo方法的语言。


3

我感到奇怪的是,没有人指出Lisp家族的有理数技巧。

认真地,打开sbcl,然后执行以下操作: (+ 1 3)您得到4。如果*( 3 2)您得到6,请立即尝试(/ 5 3)获得5/3或5分之三。

在某些情况下应该有所帮助,不是吗?


我想知道,是否可以知道结果是否需要表示为1/3还是精确的小数?
mamcx 2014年

好建议
Peter Porfy 2015年

3

我想看到的一件事是认识到doublefloat应该将to 视为扩大的转换,而floatto 则应视为double缩小(*)。这似乎违反直觉,但请考虑类型的实际含义:

  • 0.1f表示“ 13,421,773.5 / 134,217,728,正负1 / 268,435,456左右”。
  • 0.1实际上表示3,602,879,701,896,397 / 36,028,797,018,963,968,正负1 / 72,057,594,037,927,936左右”

如果一个拥有double最能代表数量“十分之一”的,并将其转换为float,则结果将是“ 13,421,773.5 / 134,217,728,正负1 / 268,435,456左右”,这是对值的正确描述。

相比之下,如果一个拥有float代表数量“十分之一”的最佳表示并将其转换为double的结果将是“ 13,421,773.5 / 134,217,728,加上或减去1 / 72,057,594,037,927,936左右” –隐含精度这是错误的,超过五千三百万。

尽管IEEE-744标准要求执行浮点数学运算,就像每个浮点数都在其范围的中心处精确地表示精确的数值一样,但这不应视为暗示浮点值实际上表示的是精确的数值。数量。相反,要求这些值位于其范围的中心的要求来自三个事实:(1)必须像操作数具有某些特定精确值一样执行计算;(2)一致且有据可查的假设比不一致或无据可循的假设更有帮助;(3)如果要做出一个一致的假设,则没有其他一致的假设会比假设一个数量代表其范围的中心更好。

顺便提一下,我记得大约25年前,有人想出了一个C的数字程序包,其中使用了“范围类型”,每个类型都由一对128位浮点数组成。所有计算将以计算每个结果的最小和最大可能值的方式进行。如果执行大的长迭代计算并得出[12.53401391134 12.53902812673]的值,则可以确信,尽管舍入误差会损失许多位数的精度,但结果仍可以合理地表示为12.54(并且不是t实际上是12.9或53.2)。令我惊讶的是,我还没有看到任何主流语言对此类类型的任何支持,特别是因为它们似乎可以与可并行处理多个值的数学单元很好地配合。

(*)实际上,在使用单精度数字时,使用双精度值来保存中间计算通常会很有帮助,因此必须对所有此类操作使用类型转换。语言可以通过具有“模糊双精度”类型来提供帮助,该类型可以将计算执行为双精度,并且可以自由地转换为单精度或单精度。如果可以标记带有类型double和返回参数的函数,double从而使它们将自动生成一个接受并返回“模糊双精度”的重载,则这将特别有用。


2

如果更多的编程语言从数据库中获取一个页面并允许开发人员指定其数字数据类型的长度和精度,则它们可以大大降低与浮点相关的错误的可能性。如果一种语言允许开发人员将变量声明为Float(2),表明他们需要一个精度为两位十进制数字的浮点数,则它可以更安全地执行数学运算。如果这样做是通过在内部将该变量表示为整数并在暴露该值之前除以100,则可以使用更快的整数算术路径来提高速度。Float(2)的语义还可以使开发人员避免在输出数据之前对数据进行舍入的需求,因为Float(2)本质上会将数据舍入到小数点后两位。

当然,当开发人员需要精度时,您需要允许其要求最大精度的浮点值。并且您会引入一些问题,即当开发人员的变量没有足够的精度时,由于中间舍入运算,同一数学运算的稍有不同的表达式可能会产生不同的结果。但是至少在数据库世界中,这似乎没什么大不了的。大多数人并没有进行要求中间结果非常精确的那种科学计算。


指定长度和精度几乎没有用。固定点基数10对于财务处理很有用,这将消除人们从浮点数中获得的很多惊喜。
David Thornley

@David-也许我遗漏了一些东西,但是定点基10数据类型与我在这里提出的有什么不同?在我的示例中,Float(2)将具有固定的2个十进制数字,并且将自动舍入到最接近的百分之一,这是您可能在简单财务计算中使用的数字。更复杂的计算将要求开发人员分配更多的十进制数字。
贾斯汀·凯夫

1
您要提倡的是定点基数10数据类型,它具有程序员指定的精度。我说的是程序员指定的精度几乎没有意义,只会导致我以前在COBOL程序中遇到的各种错误。(例如,当您更改变量的精度时,很容易会错过一个值贯穿的变量。对于另一个变量,对中间结果大小的思考要比对好结果的思考要多得多。)
David Thornley

4
Float(2)您所建议的类似名称不应被称为Float,因为这里没有任何浮动,当然也没有“小数点”。
圣保罗Ebermann

1
  • 语言具有十进制类型支持;当然,这并不能真正解决问题,但是例如have仍然没有精确和有限的表示形式。
  • 一些数据库和框架具有Money类型支持,这基本上是将美分存储为整数;
  • 有一些有理数支持的库;解决了problem的问题,但没有解决例如√2的问题;

以上这些在某些情况下适用,但实际上并不是处理浮点值的通用解决方案。真正的解决方案是理解问题并学习如何解决。如果您使用浮点计算,则应始终检查算法在数值上是否稳定。与这个问题有关的数学/计算机科学领域很大。称为数值分析


1

正如其他答案所指出的那样,避免财务软件出现浮点陷阱的唯一真正方法就是不在那儿使用它。如果您提供一个专门设计用于金融数学的库,这实际上可能是可行的。

设计用于导入浮点估计值的函数应明确标明,并提供适合该操作的参数,例如:

Finance.importEstimate(float value, Finance roundingStep)

通常,唯一避免浮点陷阱的真正方法是教育-程序员需要阅读和理解诸如每个程序员应该了解的有关浮点算法的知识

不过,可能有一些帮助:

  • 我将第二次问那些“为什么对浮点进行精确的相等测试甚至合法?”
  • 而是使用一个isNear()函数。
  • 提供并鼓励使用浮点累加器对象(与简单地将它们全部添加到常规浮点变量中相比,它们更稳定地添加浮点值序列)。

-1

大多数程序员会对COBOL做到这一点感到惊讶...在COBOL的第一个版本中没有浮点,只有十进制,而直到今天,COBOL的传统一直延续到声明数字时想到的第一件事是十进制。 ..浮点数仅在您确实需要时才使用。由于某种原因,当C出现时,没有原始的十进制类型,所以我认为,这就是所有问题的开始。


1
C没有十进制类型,因为它不是原始类型,很少有计算机具有任何类型的硬件十进制指令。您可能会问,为什么BASIC和Pascal没有它,因为它们的设计不符合金属的要求。我当时所知道的仅有COBOL和PL / I这样的语言。
David Thornley

3
@JoelFan:那你怎么用COBOL写⅓?十进制不能解决任何问题,10个基本是一样准确的基地2
vartec

2
小数解决了精确表示美元和美分的问题,这对于“面向业务”的语言很有用。但是否则,十进制是没有用的;它具有相同类型的错误(例如1/3 * 3 = 0.99999999),速度慢得多。这就是为什么在并非专为会计设计的语言中,它不是默认语言的原因。
dan04 2011年

1
而且比C早十多年的FORTRAN也没有标准的十进制支持。
dan04 2011年

1
@JoelFan:如果您有季度价值,而您需要每月价值,请猜测您需要将其乘以...不,不是0.33,而是⅓。
vartec
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.