是否可以在不求助于任意精度数据类型的情况下消除浮点错误?

| 我想知道在特定条件下是否可以在不诉诸于任意精度数据类型的情况下消除浮点错误。 问题是通常的。语言是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\"
因此,四舍五入是正确的。现在我的问题是:在以下情况下,是否有办法获得我想要的答案:使用浮点数执行的操作(数量)有严格的限制,所需的精度限制为两位小数(最大8位数字)总共),并且可以接受少量剩余的“错误地”四舍五入的答案吗? 这种情况使得用户可以输入有效的Ruby字符串,例如:
\"foo / 12.0\"
其中foo是执行字符串的上下文中提供的数字,而其中\ '12 .0 \'是用户输入的内容。想象一个包含一些免费公式字段的电子表格。字符串仅被评估为Ruby,因此12.0成为Float。我可以使用ruby_parser + ruby​​2ruby gem来构建一个解析树,将数据类型修改为Bignum,Rational,Flt库中的某些内容,十进制浮点表示形式或具有什么功能,但这很棘手,因为实际的字符串可能会变成稍微复杂一点,所以我不喜欢这样。如果没有其他可能,我将按照这种方式进行操作,但是此问题专门用于查看我是否可以避免这种情况。因此,12.0的数据类型严格为Float,结果严格为Float,我唯一能做的就是解释代码段的最终答案,并尝试对其进行“校正”,如果它四舍五入为“错误”, ' 办法。 用户所做的唯一计算涉及的数字精度为两位十进制数字(总共最多8位数字)。使用\'simple \',我的意思是浮点错误没有机会累积:我可以将这些数字中的两个相加,然后用一个整数除以一个整数,但是然后完成计算,将结果四舍五入并存储,并且随后的计算基于该四舍五入的数字。通常只涉及一个浮点错误,但我认为,如果两个可以累加,问题不会显着改变,尽管从定义上讲,残余错误率可能更大。 首先想到的是先四舍五入到十进制数字,然后四舍五入到2。但是,这不起作用。那会导致
152.48499999999999 => 152.485 => 152.49
但是也
152.4846 => 152.485 => 152.49
这不是你想要的。 我接下来想到的是,如果将浮点微调到.5边界,则将最小的增量(正如人们指出的那样,取决于所考虑的浮点值)。我主要是想知道这会多久产生一次“误报”:一个数字,其增量最小,即使该数字恰好位于.5边界以下不是由于浮点引起的错误,但是因为这仅仅是计算的结果? 第二种选择是:始终将最小的增量添加到数字上,因为无论如何,.5区域是唯一重要的区域。 编辑: 我只是改写了这个问题,以便根据评论家的建议将我的部分答案纳入评论中。我将悬赏金授予Ira Baxter积极参与讨论,尽管我尚未确信他是对的:Mark Ransom和Emilio M Bumachar似乎支持我的想法,即在实践中可能进行更正。可能在大多数情况下会产生“正确”的结果。 我仍然必须进行实验以查看结果正确的频率,并且我完全打算这样做,但是我可以花时间的时间有限,因此我还没有解决这个问题。实验并非无关紧要。     
已邀请:
        听起来您想要的是固定精度的十进制数字。一个好的实现这些功能的库将比自己一起黑客入侵更加可靠。 对于Ruby,请查看Flt库。     
“可以消除浮点错误而无需求助于无限精度的数据类型。”? 不能。浮点错误是计算机上唯一涉及数字运算的错误。如果删除所有错误,那么根据定义,您的精度是无限的。 听起来很古怪,这不是我的意图。我要指出的是,在您看来是技术问题的下方,存在一个很大的概念问题。您无法根据正确的数值正确舍入错误的数字,除非您知道正确的数值(即无限精度值或其他携带该信息的形式)。 您添加少量数字的想法可能在统计上可行,但仅在统计上可行。我建议您编写一个脚本来测试大量不超过两个小数位的随机数的案例。 (脚本还需要以无限的精度进行数学运算,以便知道进行比较的正确答案)这样,您就可以测量更正和误报。     
        如果您可以控制算术的数量(尤其是乘除法),则可以尝试简单地将所有浮点值缩放10左右的幂标度(例如scale = 4)。 您将不得不更改代码来进行输入,输出以及乘除运算。 然后,scale = 2的小数部分(例如5.10)精确存储为510。例如,读入字符串mmm.nnnn,在字符串中移动小数位刻度位置(例如,对于scale = 2 ==> mmmnn.nn,然后将字符串转换为float)。这种分数的加法/减法是精确的,不需要任何代码更改。乘法和除法会损失一些“十进制”精度,需要进行缩放。需要将x * y更改为x * y / scale的代码; x / y需要更改为x * scale / y。您可以在比例尺处将字符串取整,然后输出。 答案是使用实际十进制算术软件包的俗气版本,另一位作者提到。     
        您提到的最小增量通常称为epsilon。这是可以添加到1.0以进行显着更改的最小值。如果要将其添加到其他数字,则必须先缩放比例:
x = x + (x * epsilon)
。 epsilon的另一个定义是浮点数最大的舍入误差。此定义应为第一个定义的一半。 从理论上讲,在四舍五入之前添加一个epsilon值将产生与其校正一样多的错误。实际上,情况并非如此,因为接近偶数的数字比随机机会提示的数字更有可能被遇到。     
        我注意到在对其中一个答案的评论中,有人争辩说更改数据类型很困难。尽管如此,我将按照要求回答问题:   我想知道是否   特定条件下,有可能   删除浮点错误而无需   诉诸无限精度   数据类型。 为了获得准确的结果,您将需要使用数字的十进制浮点表示形式以及适当的数学例程。请注意,如果定点数学库使用数字的二进制表示形式,则仍可能导致二进制浮点错误。     
        在一般情况下,我会说不可能一直得到正确的答案。如您所知,四舍五入并不是答案。相反,请尝试保持尽可能长的最高精度。 但是,您确实拥有可以使用的全部功能。您可以向上,向下,四舍五入,四舍五入到无穷大,因此,如果您知道算法在做什么,则可以使用适当的函数。 我会说,添加一个“ small”值或通常被称为“ epsilon”的方法是可行的。请记住,如果原始值是负数,则必须减去它而不是添加它。另外,请注意,如果要处理所有范围的浮点值,则epsilon可能取决于该值。     
不,您不能防止浮点错误的累积,因为机器算法总是舍入运算结果以适合给定的位数。除此之外,还要考虑到许多运算的结果需要精确表示无限数量的位(例如2/10 = 0.2;但是它需要无限数量的位才能精确表示以2为基数),这就是电脑一起使用)。     
        不幸的是,这不是您的答案,但这可能会让您入门。 宾语:
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
更改为Float即可实际完成工作:
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
编辑:使用Rational更好。 nmethods重写仍然不总是有效(请参阅代码后的问题):
  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
问题:至少在1.9.3p0中,并非所有方法重写都有效:
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
    

要回复问题请先登录注册