【问题标题】:Awk Floating-point Imprecisionawk 浮点不精确
【发布时间】:2020-12-05 20:50:04
【问题描述】:

我在 Awk 中遇到了一个无法解决的浮点不精确问题。有没有简单的解决办法?

这是我复制浮点不精确问题的示例 Awk 脚本。

BEGIN {
  print "PREC = " PREC
  print "OFMT = " OFMT
  print "CONVFMT = " CONVFMT
  a = 1.2 + 3.4
  b = 8.9 - 4.3
  print "a = " a
  print "b = " b
  if ( a == b )
    print "a == b"
  else
    print "a != b"
  c = 3.2 + 5.4
  d = 9.8 - 1.2
  print "c = " c
  print "d = " d
  if ( c == d )
    print "c == d"
  else
    print "c != d"
}

这是上面脚本的输出。

PREC = 53
OFMT = %.6g
CONVFMT = %.6g
a = 4.6
b = 4.6
a != b
c = 8.6
d = 8.6
c == d

为什么 a != b 即使两者具有相同的值?然而,c == d 可以正常工作。

我假设 Awk 有一些内部浮点不精确。仅供参考,我使用的是 Gawk 4.1.4。

我尝试了 PREC、OFMT 和 CONVFMT 的各种值,但找不到合适的值。

例如将 OFMT 和 CONVFMT 更改为 %.6f:

PREC = 53
OFMT = %.6f
CONVFMT = %.6f
a = 4.600000
b = 4.600000
a != b
c = 8.600000
d = 8.600000
c == d

例如将 PREC 更改为 16:

PREC = 16
OFMT = %.6g
CONVFMT = %.6g
a = 4.6
b = 4.6
a != b
c = 8.6
d = 8.6
c == d

基本上,我希望在 BEGIN 中进行一些设置,而不是更改浮点算术和比较所在的每个表达式,因为我的实际 Awk 脚本比上面的示例长得多。

例如我宁愿不必对每个算术和比较表达式使用 sprintf,也不必在按 1e6 缩放后将每个输入数转换为整数并将每个输出数转换为 1e-6。这种方法会非常令人生畏。

仅供参考,输入文件中的浮点数最多有 6 个小数点,但它们可能没有小数点,即它们的范围为 0 到 6 个小数点。

感谢您的帮助。

HN

【问题讨论】:

  • "最多有 6 个小数点" --> 你可以将每个输入缩放 1,000,000 并四舍五入,然后执行你的数学运算吗?
  • @chux 我希望它不会如前所述。除了最终输出,程序还将打印中间。所以,我必须在每次打印时都包含转换。
  • 这能回答你的问题吗? Is floating point math broken?
  • @PresidentJamesK.Polk 不完全是。我有点知道这个问题与浮点有关,尽管不完全是。我寻找的学术解释较少,而更多的是在 Awk 中寻找一个简单而优雅的解决方案,而不必显式处理每个算术表达式的不精确性。我希望像 Awk 这样的脚本语言能够让用户不必以低廉的价格处理 C++ 等编译语言中存在的此类繁琐和混乱的问题,包括类型、声明、数组绑定、无符号与有符号数、最大整数等运行时开销。

标签: math awk floating-point comparison precision


【解决方案1】:

在这里,更高的精度对您不利。由于某些十进​​制值无法以二进制精确表示,因此您只是将数字等价的限制推向了无法满足的更高精度数字。

例如对于 53 位精度,你得到

1.2 => 1.199999999999999955591079014993738383054733
3.4 => 3.399999999999999911182158029987476766109467
8.9 => 8.900000000000000355271367880050092935562134
4.3 => 4.299999999999999822364316059974953532218933

a = 4.599999999999999644728632119949907064437866
b = 4.600000000000000532907051820075139403343201
a != b

3.2 => 3.200000000000000177635683940025046467781067
5.4 => 5.400000000000000355271367880050092935562134
9.8 => 9.800000000000000710542735760100185871124268
1.2 => 1.199999999999999955591079014993738383054733
c = 8.600000000000001421085471520200371742248535
d = 8.600000000000001421085471520200371742248535
c==d

我的建议是将PREC 设置为更合理的值(基于您的输入数据精度)。我认为 10 是一个很好的折衷方案,代码更改最少。

'BEGIN{PREC=10; ...

注意。如果你问为什么c,d 匹配,请注意它们都是分数,都是 0.2 的倍数,而 a,b 的结果是 0.3。

【讨论】:

    【解决方案2】:

    浮点数不准确,显示的答案是四舍五入的,并且不是完全浮点表示是什么,但相等性测试会计算结果的每一位。 p>

    例如,尝试用铅笔和纸将 1 除以 3,得到 0.3333333... 直到纸用完。现在乘法应该给你1.0,对吧?不,你会得到 0.9999999999...

    同样,浮点数也不能准确地表示 0.1。

    通常所做的是将相等性比较到某个限制内,称为“epsilon”。

    if absolute value of (a - b) < 0.0000001
       then print "Equal"
    

    https://www.youtube.com/watch?v=PZRI1IfStY0

    【讨论】:

    • 所有有限浮点数都是精确的——就像所有整数一样。这是许多人认为不准确的数学。
    • do not recommend那个人compare values with a tolerance。即使是名称,与“epsilon”相比也是用词不当。
    • @beaker:OP 是只像他们展示的那样做减法,还是只是为了发布到 Stack Overflow 的目的而构建的一个样本?如果他们在实际应用中有更复杂的算术,我们不知道误差范围是多少。另外,他们告诉我们输入数字的精度有限制(小数点后最多六位),但没有范围(小数点前多少位)。
    • @user14771043:没有通用的解决方案可以让计算机算术表现得像实数算术。有关于这个主题的整本书、课程和论文。对于简单的情况,有简单的解决方案。您没有很好地指定您的应用程序,无法推荐解决方案。为什么要减去从输入中读取的值?你为什么要比较它们?这是您执行的唯一算术吗?数字可以有多大?
    • @HCN:这些操作可能会产生任意大的错误。很简单,假设您已经知道 9.8-1.2-8.6 不会给出零。将该非零误差反复乘以大于一的值,它将永远增长。我不知道你在这个文件中做了什么。也许您正在减去差并将其转移到下一行并将其乘以某物(如复利)。因此,将非零误差乘以每行中的某值一千行可以将误差扩大到任意大小。需要更好地描述应用程序。
    【解决方案3】:

    GNU Awk's User's Guide - Setting precision

    如果您需要以更高的值表示浮点常数 精度高于默认值,并且不能使用命令行赋值 PREC,您应该将常量指定为字符串,或者指定为 有理数,只要有可能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-06
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 2013-08-11
      • 1970-01-01
      相关资源
      最近更新 更多