代码之家  ›  专栏  ›  技术社区  ›  Ben5e

确定字符串是否为有效的浮点值

  •  30
  • Ben5e  · 技术社区  · 16 年前

    有没有一种方法可以简单地检查字符串值是否是有效的浮点值?如果字符串不是数值,则对该字符串调用“f”会将其转换为0.0。使用float()会在传递一个无效的float字符串时引发一个异常,该字符串更接近于我想要的,但我不想处理捕获的异常。我真正想要的是像Nan这样的方法?它确实存在于float类中,但这没有帮助,因为非数字字符串在转换为float之前不能更改为0.0(使用to_f)。

    "a".to_f => 0.0
    
    "a".to_f.nan? => false
    
    Float("a") => ArgumentError: invalid value for Float(): "a"
    

    是否有一个简单的解决方案,或者我需要编写代码来检查字符串是否是有效的浮点值?

    8 回复  |  直到 8 年前
        1
  •  27
  •   Yehuda Katz    16 年前

    关于Ruby世界的一个有趣的事实是Rubinius项目的存在,它主要用纯Ruby实现Ruby及其标准库。因此,它们有一个内核浮动的纯Ruby实现,如下所示:

    def Float(obj)
      raise TypeError, "can't convert nil into Float" if obj.nil?
    
      if obj.is_a?(String)
        if obj !~ /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/
          raise ArgumentError, "invalid value for Float(): #{obj.inspect}"
        end
      end
    
      Type.coerce_to(obj, Float, :to_f)
    end
    

    这为您提供了一个正则表达式,它与运行float()时Ruby所做的内部工作相匹配,但没有异常。所以你现在可以做:

    class String
      def nan?
        self !~ /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/
      end
    end
    

    这个解决方案的好处在于,因为Rubinius运行并通过Rubyspec,所以您知道这个regex处理Ruby本身处理的边缘情况,并且您可以在字符串上调用f而不必担心!

        2
  •  35
  •   Greg Campbell    16 年前

    这里有一种方法:

    class String
      def valid_float?
        # The double negation turns this into an actual boolean true - if you're 
        # okay with "truthy" values (like 0.0), you can remove it.
        !!Float(self) rescue false
      end
    end
    
    "a".valid_float? #false
    "2.4".valid_float? #true
    

    如果您想要避免字符串的猴子补丁,您可以将其作为您控制的某个模块的类方法,当然:

    module MyUtils
      def self.valid_float?(str)
        !!Float(str) rescue false
      end
    end
    MyUtils.valid_float?("a") #false
    
        3
  •  8
  •   Ben Sand    15 年前
    # Edge Cases:
    # numeric?"Infinity" => true is_numeric?"Infinity" => false
    
    
    def numeric?(object)
    true if Float(object) rescue false
    end
    
    #Possibly faster alternative
    def is_numeric?(i)
    i.to_i.to_s == i || i.to_f.to_s == i
    end
    
        4
  •  4
  •   Sqeaky    12 年前

    我看到了关于cast+exceptions和regex的未解决的讨论,我认为我将尝试对所有内容进行基准测试,并给出一个客观的答案:

    下面是这里尝试的每种方法的最佳情况和最差情况的来源:

    require "benchmark"
    n = 500000
    
    def is_float?(fl)
      !!Float(fl) rescue false
    end
    
    def is_float_reg(fl)
      fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
    end
    
    class String
      def to_float
        Float self rescue (0.0 / 0.0)
      end
    end
    
    
    Benchmark.bm(7) do |x|
      x.report("Using cast best case") {
        n.times do |i|
          temp_fl = "#{i + 0.5}"
          is_float?(temp_fl)
        end
      }
      x.report("Using cast worst case") {
        n.times do |i|
          temp_fl = "asdf#{i + 0.5}"
          is_float?(temp_fl)
        end
      }
      x.report("Using cast2 best case") {
        n.times do |i|
          "#{i + 0.5}".to_float
        end
      }
      x.report("Using cast2 worst case") {
        n.times do |i|
          "asdf#{i + 0.5}".to_float
        end
      }
      x.report("Using regexp short") {
        n.times do |i|
          temp_fl = "#{i + 0.5}"
          is_float_reg(temp_fl)
        end
      }
      x.report("Using regexp long") {
        n.times do |i|
          temp_fl = "12340918234981234#{i + 0.5}"
          is_float_reg(temp_fl)
        end
      }
        x.report("Using regexp short fail") {
        n.times do |i|
          temp_fl = "asdf#{i + 0.5}"
          is_float_reg(temp_fl)
        end
      }
      x.report("Using regexp long fail") {
        n.times do |i|
          temp_fl = "12340918234981234#{i + 0.5}asdf"
          is_float_reg(temp_fl)
        end
      }
    
    end
    

    MRI193的结果如下:

                  user     system      total        real
    Using cast best case  0.608000   0.000000   0.608000 (  0.615000)
    Using cast worst case  5.647000   0.094000   5.741000 (  5.745000)
    Using cast2 best case  0.593000   0.000000   0.593000 (  0.586000)
    Using cast2 worst case  5.788000   0.047000   5.835000 (  5.839000)
    Using regexp short  0.951000   0.000000   0.951000 (  0.952000)
    Using regexp long  1.217000   0.000000   1.217000 (  1.214000)
    Using regexp short fail  1.201000   0.000000   1.201000 (  1.202000)
    Using regexp long fail  1.295000   0.000000   1.295000 (  1.284000)
    

    由于我们只处理线性时间算法,我认为我们使用经验测量来进行归纳。很明显,regex更加一致,只会根据传递的字符串的长度稍微波动一点。在没有失败的情况下,强制转换显然更快,而在有失败的情况下,则要慢得多。

    如果我们比较一下成功的时间,我们会发现cast best case比regex best case快了0.3秒。如果我们把它除以最坏情况下的时间量,我们就可以估计出需要多少次运行才能打破平衡,但例外情况是,为了匹配regex的速度,降低了强制转换的速度。大约6秒后,3给了我们20秒。因此,如果性能很重要,并且您希望20个测试中有1个失败,那么就使用cast+异常。

    JRuby 1.7.4的结果完全不同:

                  user     system      total        real
    Using cast best case  2.575000   0.000000   2.575000 (  2.575000)
    Using cast worst case 53.260000   0.000000  53.260000 ( 53.260000)
    Using cast2 best case  2.375000   0.000000   2.375000 (  2.375000)
    Using cast2 worst case 53.822000   0.000000  53.822000 ( 53.822000)
    Using regexp short  2.637000   0.000000   2.637000 (  2.637000)
    Using regexp long  3.395000   0.000000   3.395000 (  3.396000)
    Using regexp short fail  3.072000   0.000000   3.072000 (  3.073000)
    Using regexp long fail  3.375000   0.000000   3.375000 (  3.374000)
    

    在最好的情况下,CAST的速度只略快(约10%)。假设这个差异适合进行归纳(我不认为是),那么盈亏平衡点在200到250次运行之间,只有1次导致异常。

    所以只有当真正的异常情况发生时,才应该使用异常,这是您和您的代码库的决定。当不使用代码时,它们所在的代码可以更简单、更快。

    如果性能无关紧要,那么您可能应该遵循您的团队或代码库已经拥有的任何约定,并忽略整个答案。

        5
  •  3
  •   Hemant Kumar    16 年前

    嗯,如果你不想例外,那么也许:

    def is_float?(fl)
       fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
    end
    

    因为OP特别要求一个无例外的解决方案。基于regexp的解决方案速度稍慢:

    require "benchmark"
    n = 500000
    
    def is_float?(fl)
      !!Float(fl) rescue false
    end
    
    def is_float_reg(fl)
      fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
    end
    
    Benchmark.bm(7) do |x|
      x.report("Using cast") {
        n.times do |i|
          temp_fl = "#{i + 0.5}"
          is_float?(temp_fl)
        end
      }
      x.report("using regexp") {
        n.times do |i|
          temp_fl = "#{i + 0.5}"
          is_float_reg(temp_fl)
        end
      }
    end
    

    结果:

    5286 snippets:master!? % 
                 user     system      total        real
    Using cast  3.000000   0.000000   3.000000 (  3.010926)
    using regexp  5.020000   0.000000   5.020000 (  5.021762)
    
        6
  •  2
  •   Chris Root    9 年前

    试试这个

    def is_float(val)
      fval = !!Float(val) rescue false
      # if val is "1.50" for instance
      # we need to lop off the trailing 0(s) with gsub else no match
      return fval && Float(val).to_s == val.to_s.gsub(/0+$/,'') ? true:false
    end 
    
    s = "1000"
    is_float s
     => false 
    
    s = "1.5"
    is_float s
     => true 
    
    s = "Bob"
    is_float s
     => false
    
    n = 1000
    is_float n
     => false 
    
    n = 1.5
    is_float n
     => true
    
        7
  •  2
  •   Dorian    8 年前
    def float?(string)
      true if Float(string) rescue false
    end
    

    这种支持 1.5 , 5 , 123.456 , 1_000 但不是 1 000 , 1,000 等(如与 String#to_f )

    >> float?("1.2")
    => true
    >> float?("1")
    => true
    >> float?("1 000")
    => false
    >> float?("abc")
    => false
    >> float?("1_000")
    => true
    

    来源: https://github.com/ruby/ruby/blob/trunk/object.c#L2934-L2959

        8
  •  1
  •   Sam    16 年前

    我试图将此添加为注释,但显然注释中没有格式:

    另一方面,为什么不把它作为转换函数,比如

    class String
      def to_float
        Float self rescue (0.0 / 0.0)
      end
    end
    "a".to_float.nan? => true
    

    这当然是你一开始不想做的。我想答案是,“如果您真的不想使用异常处理,那么必须编写自己的函数,但是,为什么要这样做?”