如何消除 Perl 舍入错误

发布于 2024-09-08 20:21:27 字数 340 浏览 4 评论 0原文

考虑以下程序:

$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 技术交流群。

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

发布评论

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

评论(7

向日葵 2024-09-15 20:21:27

这是一个常见问题。

为什么我得到的是长小数(例如 19.9499999999999)而不是我应该得到的数字(例如 19.95)?

在内部,您的计算机以二进制表示浮点数。数字(如二的幂)计算机无法准确存储所有数字。一些实数在此过程中会失去精度。这是计算机存储数字方式的问题,并影响所有计算机语言,而不仅仅是 Perl。

perlnumber 显示数字表示和转换的详细信息。
要限制数字中的小数位数,您可以使用 printfsprintf 函数。有关更多详细信息,请参阅浮点算术

printf "%.2f", 10/3;
我的 $number = sprintf "%.2f", 10/3;

This is a frequently-asked question.

Why am I getting long decimals (eg, 19.9499999999999) instead of the numbers I should be getting (eg, 19.95)?

Internally, your computer represents floating-point numbers in binary. Digital (as in powers of two) computers cannot store all numbers exactly. Some real numbers lose precision in the process. This is a problem with how computers store numbers and affects all computer languages, not just Perl.

perlnumber shows the gory details of number representations and conversions.
To limit the number of decimal places in your numbers, you can use the printf or sprintf function. See the Floating Point Arithmetic for more details.

printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;
南薇 2024-09-15 20:21:27

使“使用 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.

赠佳期 2024-09-15 20:21:27

浮点精度的整个问题已经得到解答,但尽管有 bignum,您仍然会看到问题。为什么?罪魁祸首是printfbignum 是一个浅层编译指示。它只影响数字在变量和数学运算中的表示方式。尽管 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 is printf. bignum is a shallow pragma. It only affects how numbers are represented in variables and math operations. Even though bignum 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 and long 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 like bignum, but it will affect things like printf. 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.

为人所爱 2024-09-15 20:21:27

给你的家庭作业(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 be 8.

$x - (int($x) is 0.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.

森林很绿却致人迷途 2024-09-15 20:21:27

Perl 不对其内置浮点类型执行任意精度算术。所以你的初始变量 $x 是一个近似值。您可以通过执行以下操作来看到这一点:

$ perl -e 'printf "%.10f", 12345678901.234567000'
12345678901.2345676422

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:

$ perl -e 'printf "%.10f", 12345678901.234567000'
12345678901.2345676422
夏尔 2024-09-15 20:21:27

这个答案适用于我的 x64 平台,通过适应错误的规模,

sub safe_eq {
  my($var1,$var2)=@_;
  return 1 if($var1==$var2);
  my $dust;
  if($var2==0) { $dust=abs($var1); }
  else { $dust= abs(($var1/$var2)-1); }
  return 0 if($dust>5.32907051820076e-15 ); # 5.32907051820075e-15
  return 1;
}

您可以在上面的基础上解决大多数问题。

如果可以的话,请避免使用 bignum - 它极其慢 - 而且如果你必须将数字存储在数据库或 JSON 等任何地方,它不会解决任何问题。

This answer works on my x64 platform, by accommodating the scale of the errors

sub safe_eq {
  my($var1,$var2)=@_;
  return 1 if($var1==$var2);
  my $dust;
  if($var2==0) { $dust=abs($var1); }
  else { $dust= abs(($var1/$var2)-1); }
  return 0 if($dust>5.32907051820076e-15 ); # 5.32907051820075e-15
  return 1;
}

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.

夢归不見 2024-09-15 20:21:27

这与计算机进行的浮点计算的(有限)精度有关。通常,在比较浮点数时,您应该与合适的 epsilon 进行比较:

$value1 == $value2 or warn;

在大多数情况下不会按预期工作。您应该

use constant EPSILON => 1.0e-10;
abs($value1 - $value2) < EPSILON or warn;

选择 EPSILON,使其考虑到 valueX 计算的复杂性。大量的计算可能会产生大得多的 EPSILON。

正如其他人所建议的,另一种选择是:

sprintf("%.5f", value1) eq sprintf("%.5f", value2) or warn;

或使用任意精度数学库。

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:

$value1 == $value2 or warn;

won't work as expected in most cases. You should do

use constant EPSILON => 1.0e-10;
abs($value1 - $value2) < EPSILON or warn;

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:

sprintf("%.5f", value1) eq sprintf("%.5f", value2) or warn;

Or use an arbitrary precision math library.

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