为浮点比较选择一个 Epsilon 值
Choosing an Epsilon Value for Floating Point Comparisons
我的团队正在使用财务软件,该软件将货币价值暴露为 C# 浮点数加倍。有时,我们需要比较这些值以查看它们是否等于零,或者是否低于特定限制。当我注意到此逻辑中的意外行为时,我很快了解到浮点双精度中固有的舍入误差(例如 1.1 + 2.2 = 3.30000000000000003)。到目前为止,我主要使用 C# 小数来表示货币值。
我的团队决定使用 epsilon 值方法来解决这个问题。本质上,当您比较两个数字时,如果这两个数字之间的差异小于 epsilon,则认为它们相等。我们采用与以下文章中描述的类似方式实施此方法:
https://www.codeproject.com/Articles/383871/Demystify-Csharp-floating-point-equality-and-relat
我们面临的挑战是确定 epsilon 的适当值。我们的货币值可以在小数点右侧最多保留 3 位数字(刻度 = 3)。这意味着我们可以使用的最大 epsilon 是 .0001(任何更大的数字都会被忽略)。由于 epsilon 值应该很小,我们决定将它再移出一个小数点到 .00001(为了安全起见,你可以这么说)。 C# double 的精度为 at least 15 digits,所以我相信如果小数点左边的数字小于或等于 10 位数字(15 - 5 = 10,其中 5 是数字),epsilon 的这个值应该有效数字 epsilon 位于小数点右侧)。使用 10 位数字,我们可以表示十亿级的值,最高可达 9,999,999,999.999。我们可能有数亿的数字,但我们不希望达到数十亿,所以这个限制应该足够了。
我选择这个 epsilon 值的理由是否正确?我找到了很多讨论这种方法的资源,但我找不到很多资源来提供有关选择 epsilon 的指导。
你的推理似乎很有道理,但正如你已经发现的那样,这是一个复杂的问题。您可能想阅读 What Every Computer Scientist Should Know About Floating-Point Arithmetic。使用 64 位双精度数,您确实有一个 minimum 的 15 位精度。但是,您还需要验证您的输入,因为浮点数可以包含 Nan、+/- 无穷大、负零和比 15 位十进制数字大得多的 "range"。如果有人给你的库一个像 1.2E102 这样的值,你应该处理它还是认为它超出范围?同上值非常小。垃圾输入,垃圾输出,但如果您的代码检测到 "smell" 垃圾并且至少记录它可能会很好。
您可能还想考虑提供 属性 来设置精度以及不同形式的舍入。这在很大程度上取决于您使用的规范。您可能还想确定这些值是否可以表示美元以外的货币(1 美元目前 >112 日元)。
总而言之,选择您的 epsilon 低于您需要的数字(因此小数点右侧四位数字)是合理的,并为您提供了一个用于一致舍入的数字。否则 10.0129 美元和 10.0121 美元将相等,但它们的总和将是 20.025 美元而不是 20.024 美元......会计师喜欢 "foot".
我的团队正在使用财务软件,该软件将货币价值暴露为 C# 浮点数加倍。有时,我们需要比较这些值以查看它们是否等于零,或者是否低于特定限制。当我注意到此逻辑中的意外行为时,我很快了解到浮点双精度中固有的舍入误差(例如 1.1 + 2.2 = 3.30000000000000003)。到目前为止,我主要使用 C# 小数来表示货币值。
我的团队决定使用 epsilon 值方法来解决这个问题。本质上,当您比较两个数字时,如果这两个数字之间的差异小于 epsilon,则认为它们相等。我们采用与以下文章中描述的类似方式实施此方法: https://www.codeproject.com/Articles/383871/Demystify-Csharp-floating-point-equality-and-relat
我们面临的挑战是确定 epsilon 的适当值。我们的货币值可以在小数点右侧最多保留 3 位数字(刻度 = 3)。这意味着我们可以使用的最大 epsilon 是 .0001(任何更大的数字都会被忽略)。由于 epsilon 值应该很小,我们决定将它再移出一个小数点到 .00001(为了安全起见,你可以这么说)。 C# double 的精度为 at least 15 digits,所以我相信如果小数点左边的数字小于或等于 10 位数字(15 - 5 = 10,其中 5 是数字),epsilon 的这个值应该有效数字 epsilon 位于小数点右侧)。使用 10 位数字,我们可以表示十亿级的值,最高可达 9,999,999,999.999。我们可能有数亿的数字,但我们不希望达到数十亿,所以这个限制应该足够了。
我选择这个 epsilon 值的理由是否正确?我找到了很多讨论这种方法的资源,但我找不到很多资源来提供有关选择 epsilon 的指导。
你的推理似乎很有道理,但正如你已经发现的那样,这是一个复杂的问题。您可能想阅读 What Every Computer Scientist Should Know About Floating-Point Arithmetic。使用 64 位双精度数,您确实有一个 minimum 的 15 位精度。但是,您还需要验证您的输入,因为浮点数可以包含 Nan、+/- 无穷大、负零和比 15 位十进制数字大得多的 "range"。如果有人给你的库一个像 1.2E102 这样的值,你应该处理它还是认为它超出范围?同上值非常小。垃圾输入,垃圾输出,但如果您的代码检测到 "smell" 垃圾并且至少记录它可能会很好。
您可能还想考虑提供 属性 来设置精度以及不同形式的舍入。这在很大程度上取决于您使用的规范。您可能还想确定这些值是否可以表示美元以外的货币(1 美元目前 >112 日元)。
总而言之,选择您的 epsilon 低于您需要的数字(因此小数点右侧四位数字)是合理的,并为您提供了一个用于一致舍入的数字。否则 10.0129 美元和 10.0121 美元将相等,但它们的总和将是 20.025 美元而不是 20.024 美元......会计师喜欢 "foot".