【问题标题】:Convert String to Float::INFINITY将字符串转换为 Float::INFINITY
【发布时间】:2020-04-01 11:57:33
【问题描述】:

Ruby 中,Float::INFINITY.to_s 产生 "Infinity""Infinity".to_f 产生 0.0

irb(main):001:0> Float::INFINITY.to_s
=> "Infinity"
irb(main):002:0> "Infinity".to_f
=> 0.0

为什么会这样?为什么对称性被破坏了?在人类的编程语言中,我希望第二条语句的结果是Float::INFINITY。有什么方法可以使用 Ruby 将字符串转换为 Float::INFINITY-Float::INFINITY


顺便说一句。可能相关:to_json 在 Rails 中的行为也令人困惑。我希望它会像 ActiveModel::Serializer#as_json 那样筹集资金。

irb(main):001:0> {a: Float::INFINITY}.to_json
=> "{\"a\":null}"

【问题讨论】:

  • [1,2,3].to_s.to_a 也不起作用。
  • 这是一个有效点

标签: ruby types floating-point numbers


【解决方案1】:

#to_f 的工作方式是尝试在字符串的开头找到一个浮点数(忽略空格)。它忽略了其余的。如果它没有找到任何“floaty-literal-looking”的东西,它默认为0.0。这就是为什么:

''.to_f         # => 0.0
'∞'.to_f        # => 0.0
'foo'.to_f      # => 0.0
'foo 1.23'.to_f # => 0.0
' 1.23'.to_f    # => 1.23
'1.23foo'.to_f  # => 1.23

字符串'Infinity' 在这方面与任何其他纯字母字符串没有什么不同。这有点不幸,因为它破坏了对称性。正如您所指出的:

Float::INFINITY.to_s.to_f # => 0.0

但与此同时,如果 'Infinity' 确实解析为 Float::INFINITY,这可能会导致日常代码中出现一些相当奇怪且难以跟踪的错误。


显然不要这样做,但为了完整起见 - 回答最后一部分:

有什么方法可以用 Ruby 将字符串转换为 Float::INFINITY 或 -Float::INFINITY?

eval('Float::INFINITY')  # => Float::INFINITY
eval('-Float::INFINITY') # => -Float::INFINITY

【讨论】:

  • 谢谢@ndenkov。我会猜到的。你知道为什么 Ruby 决定打破对称性吗?这仅仅是为了让来自其他领域的程序员更容易接近吗?因为好吧:我的人类期望是我们有这种对称性。
  • @schmijos 我误解了这个问题。问题并不是为什么会发生这种情况,而是为什么会做出这个设计决定。为了解释,我发了another answer
【解决方案2】:

关于为什么要做出设计决定以使无穷大破坏对称性的问题:

Float::INFINITY.to_s.to_f # => 0.0

原因是 - 这是保持一致性的唯一方法。 #to_f 给您的承诺是它将解释字符串(开头)中的浮点 literal


Infinity 不是文字。如果你尝试评估它,你会得到:

NameError:未初始化的常量Infinity

Float::INFINITY 也不是文字。它是嵌套在Float 下的常量INFINITY

那为什么它只能是文字呢?嗯...我可以让#to_s 返回任何东西:

class Float
  def to_s
    'foo'
  end
end

42.0.to_s # => 'foo'

显然,期望'foo'.to_f 返回42.0 既不可能也不合理。

我们可以让Float::INFINITY.to_s返回其他东西。字符串'Infinity' 一点也不特别。它不是像 -1.239.999999999999995e+39 这样的浮点 literal


你可以反过来看——当#to_s-ed 返回一个代表它们的literal形式的字符串时,大多数浮点数。这只是一个快乐的巧合,这也是你需要#to_f他们回来。 Float::INFINITY 不返回 literal 形式的字符串版本,因为它没有 literal 形式。


就是这样。虽然我认为错过了将 设为“无限字面量”的绝佳机会,但由于不添加 unicode 字符作为拥有完整语言语法的要求,他们可能为自己省去了很多麻烦。

你能想到任何具有无穷大字面语法的编程语言吗?

【讨论】:

  • 非常感谢!这很有意义。
【解决方案3】:

回答您问题的第二部分(第一部分由@ndnenkov 出色地回答),并且不管这是一个好主意还是坏主意(正如@ndnenkov 再次指出的那样),这是一种避免字符串上的 eval 可以是这样的:

class String
  SPECIAL_FLOATS = {
    Float::INFINITY.to_s => Float::INFINITY,
    (-Float::INFINITY).to_s => -Float::INFINITY
  }

  alias_method :super_to_f, :to_f

  def to_f
    if String::SPECIAL_FLOATS.key? self
      return String::SPECIAL_FLOATS[self]
    else
      return self.super_to_f
    end
  end
end

"Infinity".to_f
# => Float::INFINITY
Float::INFINITY.to_s.to_f
# => Float::INFINITY

扩展 String 类,使其可以处理一些特殊的 Float 字面量的转换。这是否是一个好主意是一个相当复杂的话题,并且在某种程度上取决于您的项目以及您如何在代码中使用字符串。


只是为了比较,在 Windows 上的 ruby​​ 2.5.3 中(而不是 rails,因此 require "JSON"):

{a: Float::INFINITY}.to_json
# Traceback (most recent call last):
#         3: from C:/tools/ruby25/bin/irb.cmd:19:in `<main>'
#         2: from (irb):43
#         1: from (irb):43:in `to_json'
# JSON::GeneratorError (862: Infinity not allowed in JSON)

【讨论】:

  • 哇!我非常喜欢 Ruby!
  • 我真的很喜欢这种语法。以前用的挺多的,现在用的不多,现在都想念了
【解决方案4】:

有什么方法可以用 Ruby 将字符串转换为 Float::INFINITY 或 -Float::INFINITY?

您可以使用Object#const_get

:001 > Float::INFINITY
 => Infinity
:002 > string = Float::INFINITY.to_s
 => "Infinity"
:003 > Object.const_get("Float::#{string.upcase}")
 => Infinity

【讨论】:

  • 我喜欢你的想法,但这不是有点冒险吗?
  • @schmijos 我想请您澄清一下,您所说的“有风险”是什么意思? :) #const_get 只是从Object 常量空间中选择需要的常量,你可以通过Object.constants 检查它,我无法想象它有任何风险,说实话':)
  • 我从网上收到字符串,不希望任何人强​​制使用Float::NAN。但无论如何,我没有投反对票 ;-)
  • @schmijos 在元编程中使用来自网络的参数而不进行任何验证总是有风险的 ':) 您可以添加任何简单的验证,例如: 1. return unless some_web_param.match /infinity/i; 2.do some magic with 'some_web_param':)
  • @schmijos 我不担心没有任何 cmets 的投票':)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-22
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-09
相关资源
最近更新 更多