如何消除 Perl 舍入错误
考虑以下程序:
$x=12345678901.234567000;
$y=($x-int($x))*1000000000;
printf("%f:%f\n",$x,$y);
以下是打印内容:
12345678901.234568:234567642.211914
我期待的是:
12345678901.234567:234567000
这似乎是 Perl 中的某种舍入问题。
我该如何更改它以获取 234567000
?
我做错了什么吗?
Consider the following program:
$x=12345678901.234567000;
$y=($x-int($x))*1000000000;
printf("%f:%f\n",$x,$y);
Here's what is prints:
12345678901.234568:234567642.211914
I was expecting:
12345678901.234567:234567000
This appears to be some sort of rounding issue in Perl.
How could I change it to get 234567000
instead?
Did I do something wrong?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这是一个常见问题。
This is a frequently-asked question.
使“使用 bignum;”程序的第一行。
其他答案解释了使用浮点运算时会发生什么——末尾的一些数字并不是真正的答案的一部分。这是为了使计算能够在合理的时间和空间内完成。如果您愿意使用无限的时间和空间来处理数字,那么您可以使用任意精度的数字和数学,这就是“使用 bignum”所实现的。它速度较慢并且占用更多内存,但它的工作原理就像您在小学学到的数学一样。
一般来说,在将程序转换为任意精度数学之前,最好详细了解浮点数学的工作原理。只有在非常奇怪的情况下才需要它。
Make "use bignum;" the first line of your program.
Other answers explain what to expect when using floating point arithmetic -- that some digits towards the end are not really part of the answer. This is to make the computations do-able in a reasonable amount of time and space. If you are willing to use unbounded time and space to work with numbers, then you can use arbitrary-precision numbers and math, which is what "use bignum" enables. It's slower and uses more memory, but it works like math you learned in elementary school.
In general, it's best to learn more about how floating point math works before converting your program to arbitrary-precision math. It's only needed in very strange situations.
浮点精度的整个问题已经得到解答,但尽管有
bignum
,您仍然会看到问题。为什么?罪魁祸首是printf
。bignum
是一个浅层编译指示。它只影响数字在变量和数学运算中的表示方式。尽管bignum
使 Perl 能够正确地进行数学计算,printf
仍然是用 C 实现的。%f
获取您的精确数字并将其转回为不精确的浮点数。只需使用
print
打印您的数字,它们应该就可以了。您必须手动格式化它们。您可以做的另一件事是使用 -Duse64bitint -Duselongdouble 重新编译 Perl,这将强制 Perl 在内部使用 64 位整数和 long double 浮点数。这将为您提供更高的准确性、更一致且几乎没有性能成本(
bignum
对于数学密集型代码来说有点消耗性能)。它不像bignum
那样 100% 准确,但它会影响printf
之类的东西。然而,以这种方式重新编译 Perl 会使其二进制不兼容,因此您必须重新编译所有扩展。如果您这样做,我建议在不同的位置(/usr/local/perl/64bit
或其他位置)安装一个新的 Perl,而不是尝试管理共享同一库的并行 Perl 安装。The whole issue of floating point precision has been answered, but you're still seeing the problem despite
bignum
. Why? The culprit isprintf
.bignum
is a shallow pragma. It only affects how numbers are represented in variables and math operations. Even thoughbignum
makes Perl do the math right,printf
is still implemented in C.%f
takes your precise number and turns it right back into an imprecise floating point number.Print your numbers with just
print
and they should do fine. You'll have to format them manually.The other thing you can do is to recompile Perl with
-Duse64bitint -Duselongdouble
which will force Perl to internally use 64 bit integers andlong double
floating point numbers. This will give you a lot more accuracy, more consistently and almost no performance cost (bignum
is a bit of a performance hog for math intensive code). Its not 100% accurate likebignum
, but it will affect things likeprintf
. However, recompiling Perl this way makes it binary incompatible, so you're going to have to recompile all your extensions. If you do this, I suggest installing a fresh Perl in a different location (/usr/local/perl/64bit
or something) rather than trying to manage parallel Perl installs sharing the same library.给你的家庭作业(Googlework?):计算机如何表示浮点数?
您只能拥有有限数量的精确数字,超出的所有内容都只是基数转换(二进制到十进制)的噪音。这也是为什么
$x
的最后一位数字看起来是8
的原因。$x - (int($x)
是0.23456linenoise
,它也是一个浮点数。乘以 1000000000,它给出另一个浮点数,具有更多的随机数字是从基础的不可通约性中拉出来的。Homework (Googlework?) for you: How are floating point numbers represented by computers?
You can only have a limited number of precise digits, everything beyond that is just the noise from base conversion (binary to decimal). That is also why the last digit of your
$x
appears to be8
.$x - (int($x)
is0.23456linenoise
, which is also a floating point number. Multiplied by 1000000000, it gives another floating point number, with more random digits pulled from the incommensurability of the bases.Perl 不对其内置浮点类型执行任意精度算术。所以你的初始变量
$x
是一个近似值。您可以通过执行以下操作来看到这一点:Perl does not do arbitrary precision arithmetic for its built-in floating point types. So your initial variable
$x
is an approximation. You can see this by doing:这个答案适用于我的 x64 平台,通过适应错误的规模,
您可以在上面的基础上解决大多数问题。
如果可以的话,请避免使用 bignum - 它极其慢 - 而且如果你必须将数字存储在数据库或 JSON 等任何地方,它不会解决任何问题。
This answer works on my x64 platform, by accommodating the scale of the errors
You can build on the above to solve most of your problems.
Avoid bignum if you can - it's stupendously slow - plus it will not solve any problems if you've got to store your numbers anyplace like a DB or in JSON etc.
这与计算机进行的浮点计算的(有限)精度有关。通常,在比较浮点数时,您应该与合适的 epsilon 进行比较:
在大多数情况下不会按预期工作。您应该
选择 EPSILON,使其考虑到 valueX 计算的复杂性。大量的计算可能会产生大得多的 EPSILON。
正如其他人所建议的,另一种选择是:
或使用任意精度数学库。
This has to do with the (limited) accuracy of the floating point computations a computer does. Generally when comparing floating point numbers you should compare with a suitable epsilon:
won't work as expected in most cases. You should do
EPSILON should be chosen such that it takes into account the complexity of the computations for valueX. A large computation might lead to a much, much larger EPSILON.
The other option is, as suggested by others:
Or use an arbitrary precision math library.