浮点算术 - Double 类型的模运算符

发布于 2024-09-03 02:56:22 字数 1112 浏览 1 评论 0原文

所以我试图找出为什么模运算符返回如此大的异常值。

如果我有代码:

double result = 1.0d % 0.1d;

它将给出0.099999999999999995的结果。我期望值为 0

请注意,使用除法运算符不存在此问题 - double result = 1.0d / 0.1d;

将给出结果 10.0,这意味着余数应该0 >。

让我明确一点:我对错误的存在并不感到惊讶,令我惊讶的是与实际的数字相比,错误是如此之大。 0.0999 ~= 0.1 并且 0.1 与 0.1d 处于同一数量级,仅与 1.0d 相差一个数量级。它不像你可以将它与 double.epsilon 进行比较,或者说“如果其差异 < 0.00001,则它相等”。

我在 StackOverflow 上阅读了有关此主题的以下文章 one 两个 三个,等等。

谁能建议解释一下为什么这个错误这么大?任何避免将来遇到问题的建议(我知道我可以使用十进制代替,但我担心它的性能)。

编辑:我应该特别指出,我知道 0.1 是一个无限以二进制重复一系列数字 - 这有什么关系吗?

So I'm trying to figure out why the modulo operator is returning such a large unusual value.

If I have the code:

double result = 1.0d % 0.1d;

it will give a result of 0.09999999999999995. I would expect a value of 0

Note this problem doesn't exist using the dividing operator -
double result = 1.0d / 0.1d;

will give a result of 10.0, meaning that the remainder should be 0.

Let me be clear: I'm not surprised that an error exists, I'm surprised that the error is so darn large compared to the numbers at play. 0.0999 ~= 0.1 and 0.1 is on the same order of magnitude as 0.1d and only one order of magnitude away from 1.0d. Its not like you can compare it to a double.epsilon, or say "its equal if its < 0.00001 difference".

I've read up on this topic on StackOverflow, in the following posts one two three, amongst others.

Can anyone suggest explain why this error is so large? Any any suggestions to avoid running into the problems in the future (I know I could use decimal instead but I'm concerned about the performance of that).

Edit: I should specifically point out that I know that 0.1 is an infinitely repeating series of numbers in binary - does that have anything to do with it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

中二柚 2024-09-10 02:56:22

出现该错误的原因是 double 无法精确表示 0.1——它可以表示的最接近值是 0.100000000000000005551115123126。现在,当你用 1.0 除以它时,它会得到一个略小于 10 的数字,但双精度数又不能准确表示它,因此它最终会向上舍入到 10。但是当你执行 mod 时,它可以给你稍微小于 10 的数字。余数小于 0.1。

由于 0 = 0.1 mod 0.1,mod 中的实际误差是 0.1 - 0.09999999... -- 非常小。

如果将 % 运算符的结果加上 9 * 0.1,它将再次为您提供 1.0。

编辑

有关舍入内容的更多详细信息 - 特别是因为这个问题是混合精度危险的一个很好的例子。

浮点数的 a % b 计算方式通常为 a - (b * Floor(a/b))。问题是,它可能会以比这些操作更高的内部精度一次性完成(并且在每个阶段将结果四舍五入为 fp 数字),因此它可能会给您带来不同的结果。很多人看到的一个例子是,Intel x86/x87 硬件使用 80 位精度进行中间计算,而仅使用 64 位精度计算内存中的值。因此,上面等式中 b 中的值来自内存,因此是一个不完全是 0.1 的 64 位 fp 数字(感谢 dan04 提供了精确值),因此当它计算 1.0/0.1 时,得到 9.999999999999999944488848768742172978818416595458984375 (四舍五入到 80 位)。现在,如果将其四舍五入为 64 位,则为 10.0,但如果保留 80 位内部并对其进行取整,则它会截断为 9.0,从而得到 .0999999999999999500399638918679556809365749359130859375 作为最终答案。

因此,在这种情况下,您会看到一个很大的明显错误,因为您使用的是非连续阶跃函数(下限),这意味着内部值的微小差异可能会导致您超过该阶跃。但由于 mod 本身是一个不连续的阶跃函数,这是可以预料的,这里的实际误差是 0.1-0.0999...因为 0.1 是 mod 函数范围内的不连续点。

The error comes about because a double can't exactly represent 0.1 -- the closest it can represent is something like 0.100000000000000005551115123126. Now when you divide 1.0 by that it gives you a number slightly less than 10, but again a double can't exactly represent it, so it ends up rounding up to 10. But when you do the mod, it can give you that slightly less than 0.1 remainder.

since 0 = 0.1 mod 0.1, the actual error in the mod is 0.1 - 0.09999999... -- very small.

If you add the result of the % operator to 9 * 0.1, it will give you 1.0 again.

Edit

A bit more detail on the rounding stuff -- particularly as this problem is a good example of the perils of mixed precision.

The way a % b for floating point numbers is usually computed is as a - (b * floor(a/b)). The problem is that it may be done all at once with more internal precision than you'd get with those operations (and rounding the result to a fp number at each stage), so it might give you a different result. One example that a lot of people see is with the Intel x86/x87 hardware is using 80-bit precision for intermediate computations and only 64-bit precision for values in memory. So the value in b in the equation above is coming from memory and is thus a 64-bit fp number that's not quite 0.1 (thank dan04 for the exact value), so when it computes 1.0/0.1 it gets 9.99999999999999944488848768742172978818416595458984375 (rounded to 80 bits). Now if you round that to 64 bits, it would be 10.0, but if you keep the 80 bit internal and do the floor on it, it truncates to 9.0 and thus gets .0999999999999999500399638918679556809365749359130859375 as the final answer.

So in this case, you're seeing a large apparent error because you're using a noncontinuous step function (floor) which means that a very tiny difference in an internal value can push you over the step. But since mod is itself a noncontinuous step function thats to be expected and the real error here is 0.1-0.0999... as 0.1 is the discontinuous point in the range of the mod function.

你列表最软的妹 2024-09-10 02:56:22

0.1 无法用二进制精确表示这一事实与此密切相关。

如果 0.1 可以表示为 double,您将获得最接近您要计算的操作的实际结果(假设“最近”舍入模式)的可表示 double。

因为它不能,所以您将获得最接近于与您尝试计算的操作完全不同的操作的可表示双精度。

另请注意, / 是一个大部分连续的函数(参数上的微小差异通常意味着结果上的微小差异,虽然导数在零附近但在零的同一侧可能很大,但至少参数的额外精度有帮助) 。另一方面 % 不是连续的:无论您选择什么精度,总会有一些参数,第一个参数的任意小的表示误差意味着结果的大误差。

IEEE 754 的指定方式是,假设参数正是您想要的,您只能保证 one 浮点运算结果的近似值。如果参数不完全是您想要的,您需要切换到其他解决方案,例如区间算术或对程序的良好条件性进行分析(如果它在浮点数上使用 %,则可能不太好) -条件)。

The fact that 0.1 cannot be represented exactly in binary has everything to do with it.

If 0.1 could be represented as a double, you would be getting the representable double nearest (assuming "nearest" rounding mode) to the actual result of the operation you want to compute.

Since it can't, you are getting the representable double nearest to an operation that is entirely different from the one you were trying to compute.

Also note that / is a mostly continuous function (a small difference on the arguments generally means a small difference on the result, and while the derivative can be large near but on a same side of zero, at least additional precision for the arguments helps). On the other hand % is not continuous: whatever the precision you choose, there will always be arguments for which an arbitrarily small representation error on the first argument means a large error on the result.

The way IEEE 754 is specified, you only get guarantees on the approximation of the result of one floating-point operation assuming the arguments are exactly what you want. If the arguments are not exactly what you want, you need to switch to other solutions, such as interval arithmetic or analysis of the well-conditionedness of your program (if it uses % on floating-point numbers, it is likely not to be well-conditioned).

笨笨の傻瓜 2024-09-10 02:56:22

这并不完全是计算中的“错误”,而是事实上您一开始就没有真正的 0.1。

问题是 1.0 可以用二进制浮点精确表示,但 0.1 不能,因为它不能精确地从 2 的负幂构造出来。 (它是 1/16 + 1/32 + ...)

所以你并没有真正得到 1.0 % 0.1,机器剩下来计算 1.0 % 0.1 +- 0.00 ...然后它诚实地报告它得到的结果结果...

为了有很大的余数,我认为 % 的第二个操作数必须略大于 0.1,从而阻止最终除法,并导致几乎整个 0.1 都是手术。

It's not precisely an "error" in the calculation but the fact that you never really had 0.1 to start with.

The problem is that 1.0 can be represented exactly in binary floating point but 0.1 cannot, because it can't be constructed exactly from negative powers of two. (It's 1/16 + 1/32 + ...)

So you aren't really getting 1.0 % 0.1, the machine is left to compute 1.0 % 0.1 +- 0.00... and then it reports honestly what it got as a result...

In order to have a large remainder, I suppose the second operand of % must have been slightly over 0.1, preventing the final division, and resulting in almost the entire 0.1 being the result of the operation.

千里故人稀 2024-09-10 02:56:22

你看到的误差很小;乍一看,它看起来很大。
您的结果(四舍五入后显示)是
0.09999999999999995 == (0.1 - 5e-17) 当您期望 a 为 0 时
%0.1操作。
但请记住,这几乎是 0.1,并且 0.1 % 0.1 == 0

所以这里的实际错误是-5e-17。我会称之为小。

根据您需要该数字的目的,最好编写:

double result = 1.0 % 0.1;
结果=结果>=0.1/2?结果 - 0.1:结果;

The error you see is small; it only looks large at first glance.
Your result (after rounding for display) was
0.09999999999999995 == (0.1 - 5e-17) when you expected 0 from a
% 0.1 operation.
But remember that this is almost 0.1, and 0.1 % 0.1 == 0.

So your actual error here is -5e-17. I would call this small.

Depending on what you need the number for, it might be better to write:

double result = 1.0 % 0.1;
result = result >= 0.1/2 ? result - 0.1 : result;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文