Floating point 是否可以在不使用任意精度数据类型的情况下删除浮点错误?

Floating point 是否可以在不使用任意精度数据类型的情况下删除浮点错误?,floating-point,rounding,precision,Floating Point,Rounding,Precision,我想知道,在特定条件下,是否有可能在不使用任意精度数据类型的情况下删除浮点错误 这是一个常见的问题。该语言为Ruby,但适用于任何语言: f = 1829.82 => 1829.82 f / 12.0 => 152.485 (f / 12.0).round(2) => 152.48 为什么不是152.49?因为由于浮点的精度有限: format("%18.14f", f) => "1829.81999999999994" format("%18.14f", f /

我想知道,在特定条件下,是否有可能在不使用任意精度数据类型的情况下删除浮点错误

这是一个常见的问题。该语言为Ruby,但适用于任何语言:

f = 1829.82
=> 1829.82

f / 12.0
=> 152.485

(f / 12.0).round(2)
=> 152.48
为什么不是152.49?因为由于浮点的精度有限:

format("%18.14f", f)
=> "1829.81999999999994"

format("%18.14f", f / 12.0)
=> "152.48499999999999"
所以四舍五入是正确的。现在我的问题是:考虑到以下情况,有没有办法得到我想要的答案:使用float执行的操作(数量)有很强的限制,所需的精度限制在小数点后两位(总共最多8位),少量剩余的“错误”四舍五入答案是可以接受的

这种情况下,用户可以输入有效的Ruby字符串,如:

"foo / 12.0"
其中,foo是在执行字符串的上下文中提供的数字,但“12.0”是用户输入的数字。想象一个电子表格,其中包含一些自由公式字段。字符串被简单地计算为Ruby,因此12.0变成了一个Float。我可以使用ruby_parser+ruby2ruby gems构建一个解析树,将数据类型修改为Bignum、Rational、Flt库中的一些东西、十进制浮点表示法或其他东西,但这很棘手,因为实际字符串可能会变得更复杂,所以我不想这样做。如果没有其他可能的话,我会走这条路,但这个问题就是为了看看我是否可以避免这条路。因此,12.0的数据类型是严格浮点型,结果是严格浮点型,我唯一能做的就是解释代码片段的最终答案,并尝试“纠正”它,如果它以“错误”的方式舍入

用户所做的唯一计算涉及精度为两位小数(最多8位)的数字。对于“simple”,我的意思是浮点错误没有累积的机会:我可以将其中两个数字相加,然后将其中一个除以一个整数,但随后计算完成,结果被舍入并存储,任何后续计算都基于舍入的数字。通常只涉及一个浮点错误,但我认为如果两个浮点错误可以累加,则问题不会显著改变,尽管根据定义,剩余错误率可能会更大

首先想到的可能是先四舍五入到3位小数,然后四舍五入到2位。然而,这是行不通的。这将导致

152.48499999999999 => 152.485 => 152.49
而且

152.4846 => 152.485 => 152.49
这不是你想要的

接下来我想到的是,如果浮点数超过了.5边界,则向浮点数添加尽可能小的增量(正如人们所指出的,这取决于所考虑的浮点值)。我主要想知道这会导致“假阳性”的频率有多高:一个加上最小增量的数字,尽管它刚好低于.5边界不是因为浮点错误,而是因为它只是计算的结果

第二种选择是:始终将最小增量添加到数字中,因为.5区域是唯一重要的区域

编辑: 正如迪金斯所建议的那样,我只是重写了这个问题,将我的部分答案纳入了评论中。我将奖金授予了Ira Baxter,因为他积极参与了讨论,尽管我还不确信他是对的:Mark Ransom和Emilio m Bumachar似乎支持我的观点,即纠正是可能的,在实践中,在大多数情况下,可能会产生“正确”的结果


我仍然需要进行实验,看看结果是否正确,我完全打算这样做,但我可以花在这方面的时间有限,所以我还没有抽出时间来做。这个实验并不简单。

听起来你想要的是固定精度的十进制数。一个好的实现这些功能的库将比你自己把东西拼凑起来更可靠


对于Ruby,请查看。

如果您可以控制算术运算量(特别是乘和除),您可以尝试简单地将所有浮点值按10的幂比例缩放(比如比例=4)。 您必须更改代码来执行输入、输出、乘法和除法

然后,scale=2个小数点,例如5.10,被准确地存储为510。输入需要准确输入;e、 例如,读入字符串mmm.nnnn,移动字符串中的小数点位置(例如,对于scale=2==>mmmnn.nn,然后将字符串转换为float)。这种分数的加法/减法是精确的,不需要任何代码更改。乘法和除法会损失一些“十进制”精度,需要进行缩放;表示x*y需要更改为x*y/刻度的代码;x/y需要更改为x*比例/y。您可以在缩放点将字符串四舍五入,然后输出它


这个答案是另一张海报中提到的使用真正的十进制算术软件包的拙劣版本。

在一般情况下,我认为不可能一直得到正确的答案。正如你自己发现的,四舍五入两次并不是答案。相反,尽量长时间保持最高精度

然而,你有一个完整的功能库供你使用。你可以向上取整,向下取整,向零取整,向无穷大取整,所以如果你知道你的算法在做什么,你可以使用适当的函数


我想说,添加一个“小”值,或者通常称之为“ε”,是一种可行的方法。请记住,如果原始值为负值,则必须对其进行减法,而不是相加。另外,请注意,如果您处理的是全范围的浮点值,则epsilon可能取决于该值。

否,您无法防止浮点错误的累积,因为机器算术总是将运算结果舍入给定的位数。加上
class Object
  # Return only the methods not present on basic objects
  def local_methods
    (self.methods - Object.new.methods).sort
  end
end
module Hooker
  module ClassMethods
  private
    def following(*syms, &block)
      syms.each do |sym| # For each symbol
        str_id = "__#{sym}__hooked__"
        unless private_instance_methods.include?(str_id)
          alias_method str_id, sym    # Backup original method
          private str_id         # Make backup private
          define_method sym do |*args|  # Replace method
            ret = __send__ str_id, *args # Invoke backup
            rval=block.call(self,       # Invoke hook
             :method => sym, 
             :args => args,
             :return => ret
            )
            if not rval.nil?
              ret=rval[:ret]
            end
            ret # Forward return value of method
          end
        end
      end
    end
  end

  def Hooker.included(base)
    base.extend(ClassMethods)
  end
end
if 0.1**2 != 0.01 # patch Float so it works by default
  class Float
    include Hooker
    0.1.local_methods.each do |op|
      if op != :round
        following op do |receiver, args|
          if args[:return].is_a? Float
            ret=args[:return].round Float::DIG
            ret=Hash[:ret => ret]
          end
          ret
        end
      end
    end
  end
end
  class Float
    include Hooker
    0.1.local_methods.each do |op|
      if op != :round
        following op do |receiver, args|
          if args[:return].is_a? Float
            argsin=[]
            args[:args].each do |c|
              argsin=c.rationalize
            end
            rval=receiver.rationalize.send(
                args[:method], 
                argsin
               )
            ret=Hash[:ret => rval.to_f]
          end
          ret
        end
      end
    end
  end
pry(main)> 6543.21 % 137.24
=> 92.93
[... but ...]
pry(main)> 19.5.send(:-.to_sym, 16.8)
=> 2.7
pry(main)> 19.5 - 16.8
=> 2.6999999999999993