对浮点算术及其缺点的误解是编程中令人吃惊和困惑的主要原因(请考虑与Stack Overflow有关的“数字未正确添加”的问题数)。考虑到许多程序员尚未了解其含义,因此它有可能引入许多细微的错误(尤其是在财务软件中)。有什么可以编程语言做,以避免其对那些不熟悉的概念陷阱,同时还提供了高速时的精度是不是为那些关键的做明白的概念?
对浮点算术及其缺点的误解是编程中令人吃惊和困惑的主要原因(请考虑与Stack Overflow有关的“数字未正确添加”的问题数)。考虑到许多程序员尚未了解其含义,因此它有可能引入许多细微的错误(尤其是在财务软件中)。有什么可以编程语言做,以避免其对那些不熟悉的概念陷阱,同时还提供了高速时的精度是不是为那些关键的做明白的概念?
Answers:
您说“特别是用于财务软件”,这使我心烦意乱:金钱不是浮标,而是整数。
当然,它看起来像个漂浮物。它在那里有一个小数点。但这仅仅是因为您已经习惯了使问题混淆的部门。金钱总是整数。在美国,是美分。(在某些情况下,我认为可能是米尔斯,但现在暂时忽略它。)
因此,当您说$ 1.23时,实际上是123美分。总是,总是,总是用这些术语来做数学,你会没事的。有关更多信息,请参见:
直接回答这个问题,编程语言应该只包含Money类型作为合理的原语。
更新
好吧,我应该只说“总是”两次,而不是三遍。金钱的确是一个整数。那些有其他想法的人可以尝试寄给我0.3美分,并在您的银行对帐单上显示结果。但是正如评论员指出的那样,当您需要对类似货币的数字进行浮点运算时,很少有例外。例如,某些种类的价格或利息计算。即使那样,也应该将其视为例外。金钱以整数形式进出,因此您的系统越接近这个数字,它就会越聪明。
Decimal
是处理此问题的唯一理智的系统,您的评论“
在许多情况下,为十进制类型提供支持会有所帮助。许多语言都有十进制类型,但使用率不高。
了解使用实数表示时发生的近似值很重要。同时使用十进制和浮点类型9 * (1/9) != 1
是正确的声明。当常量时,优化器可以优化计算,使其正确。
提供近似运算符会有所帮助。但是,这样的比较是有问题的。请注意,.9999万亿美元大约等于1万亿美元。您能把差额存入我的银行帐户吗?
0.9999...
一万亿美元实际上等于一万亿美元。
0.99999...
。它们都在某个时间截断,导致不平等。 0.9999
对于工程来说已经足够了。出于财务目的,并非如此。
当我上大学时,我们被告知在计算机科学的第一年(二年级)演讲中要做的事情(该课程也是大多数科学课程的前提条件)
我回想起一位讲师的话:“浮点数是近似值。为钱使用整数类型。对BCD编号使用FORTRAN或其他语言进行精确计算。” (然后,他指出了近似值,使用的经典例子0.2不可能在二进制浮点数中准确表示)。那个星期在实验室练习中也发现了这一点。
同一讲课:“如果必须从浮点数中获得更高的准确性,请对术语进行排序。将小数字加在一起,而不是大数字。” 那记在我的脑海。
几年前,我有一些球面几何形状,需要非常精确且仍然快速。PC机上的80位double并没有减少它,因此我在程序中添加了一些类型,以便在执行交换操作之前对术语进行排序。问题解决了。
在抱怨吉他的质量之前,请先学习演奏。
四年前,我有一个为JPL工作的同事。他对我们在某些事情上使用FORTRAN表示怀疑。(我们需要离线计算的超精确数值模拟。)“我们用C ++取代了所有FORTRAN”,他自豪地说道。我不再怀疑他们为什么错过了一颗行星。
1.0 + 0.1 + ... + 0.1
将返回计算(重复10次)1.0
。做它的其他方式回合,你得到的中间结果0.2
,0.3
......,1.0
终于2.0
。这是一个极端的示例,但是对于现实的浮点数,也会发生类似的问题。基本思想是,将大小相近的数字相加会导致最小的误差。从最小的数字开始,因为它们的总和更大,因此更适合于添加更大的数字。
警告:浮点类型System.Double缺乏直接相等测试的精度。
double x = CalculateX();
if (x == 0.1)
{
// ............
}
我不相信在语言层面上可以或应该做的任何事情。
Decimal
进行相等性测试时好或坏。之间的差异1.0m/7.0m*7.0m
和1.0m
可能幅度小于之间的区别的许多订单1.0/7.0*7.0
,但它不是零。
默认情况下,语言应对非整数使用任意精度有理数。
那些需要优化的人可以总是要求浮动。在C和其他系统编程语言中将它们用作默认值是有意义的,但在当今流行的大多数语言中却没有。
double
。如果计算需要精确到百万分之几,那么花一微秒的时间来精确到十亿分之几最好,而不是绝对精确地花费一秒。
涉及浮点数的两个最大问题是:
第一种故障只能通过提供包括值和单位信息的复合类型来纠正。例如,一个length
或area
包含单位的值(分别为米或平方米或英尺和平方英尺)。否则,您必须勤于始终使用一种类型的度量单位,并且仅当我们与人类共享答案时才转换为另一种。
第二类失败是概念上的失败。当人们认为失败是失败时,失败就显现出来了绝对数字。它影响相等性运算,累积舍入误差等。例如,对于一个系统,两个测量在一定误差范围内是等效的可能是正确的。即,当您不关心小于+/- .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 ^ 64实际上很小,可以避免整数算术的典型问题。
x
== y
并不意味着对进行计算x
将产生与对进行相同计算的结果相同的结果y
。
我感到奇怪的是,没有人指出Lisp家族的有理数技巧。
认真地,打开sbcl,然后执行以下操作:
(+ 1 3)
您得到4。如果*( 3 2)
您得到6,请立即尝试(/ 5 3)
获得5/3或5分之三。
在某些情况下应该有所帮助,不是吗?
我想看到的一件事是认识到double
,float
应该将to 视为扩大的转换,而float
to 则应视为double
缩小(*)。这似乎违反直觉,但请考虑类型的实际含义:
如果一个拥有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
从而使它们将自动生成一个接受并返回“模糊双精度”的重载,则这将特别有用。
如果更多的编程语言从数据库中获取一个页面并允许开发人员指定其数字数据类型的长度和精度,则它们可以大大降低与浮点相关的错误的可能性。如果一种语言允许开发人员将变量声明为Float(2),表明他们需要一个精度为两位十进制数字的浮点数,则它可以更安全地执行数学运算。如果这样做是通过在内部将该变量表示为整数并在暴露该值之前除以100,则可以使用更快的整数算术路径来提高速度。Float(2)的语义还可以使开发人员避免在输出数据之前对数据进行舍入的需求,因为Float(2)本质上会将数据舍入到小数点后两位。
当然,当开发人员需要精度时,您需要允许其要求最大精度的浮点值。并且您会引入一些问题,即当开发人员的变量没有足够的精度时,由于中间舍入运算,同一数学运算的稍有不同的表达式可能会产生不同的结果。但是至少在数据库世界中,这似乎没什么大不了的。大多数人并没有进行要求中间结果非常精确的那种科学计算。
Float(2)
您所建议的类似名称不应被称为Float
,因为这里没有任何浮动,当然也没有“小数点”。
正如其他答案所指出的那样,避免财务软件出现浮点陷阱的唯一真正方法就是不在那儿使用它。如果您提供一个专门设计用于金融数学的库,这实际上可能是可行的。
设计用于导入浮点估计值的函数应明确标明,并提供适合该操作的参数,例如:
Finance.importEstimate(float value, Finance roundingStep)
通常,唯一避免浮点陷阱的真正方法是教育-程序员需要阅读和理解诸如每个程序员应该了解的有关浮点算法的知识。
不过,可能有一些帮助:
isNear()
函数。大多数程序员会对COBOL做到这一点感到惊讶...在COBOL的第一个版本中没有浮点,只有十进制,而直到今天,COBOL的传统一直延续到声明数字时想到的第一件事是十进制。 ..浮点数仅在您确实需要时才使用。由于某种原因,当C出现时,没有原始的十进制类型,所以我认为,这就是所有问题的开始。