Конвертировать строку в число с плавающей точкой :: INFINITY - PullRequest
1 голос
/ 01 апреля 2020

In 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. Есть ли способ преобразовать строку в Float::INFINITY или -Float::INFINITY с помощью Ruby?


Кстати. может быть связано: поведение to_json в Rails также сбивает с толку. Я ожидаю, что он повысится, как ActiveModel::Serializer#as_json.

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

Ответы [ 4 ]

2 голосов
/ 01 апреля 2020

Способ работы #to_f заключается в том, что он пытается найти число с плавающей точкой в ​​начале строки (игнорируя пробелы). Остальное игнорирует. Если он не находит ничего «выглядящего буквально», по умолчанию он равен 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, это могло бы привести к некоторым довольно странным и трудным для отслеживания ошибок в повседневном коде.


Очевидно, не делайте этого, но для полноты - ответьте на последнюю часть:

Есть ли способ преобразовать строку в Float :: INFINITY или -Float: : БЕСКОНЕЧНОСТЬ с Ruby?

eval('Float::INFINITY')  # => Float::INFINITY
eval('-Float::INFINITY') # => -Float::INFINITY
1 голос
/ 01 апреля 2020

На вопрос, почему проектное решение было принято так, что бесконечность нарушает симметрию:

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

Причина в том, что это единственный способ иметь согласованность. Обещание, которое #to_f дает вам, заключается в том, что он будет интерпретировать число с плавающей запятой литерал в (в начале) строки.


Infinity не является буквальный . Если вы попытаетесь оценить его, вы получите:

NameError: неинициализированная константа Infinity

Float::INFINITY не является литералом либо. Это константа INFINITY, вложенная в Float.

Почему тогда это должны быть только литералы? Ну ... я могу заставить #to_s вернуть все что угодно:

class Float
  def to_s
    'foo'
  end
end

42.0.to_s # => 'foo'

Очевидно, что и 1032 *, и невозможно разумно ожидать 42.0.

Мы можем сделать Float::INFINITY.to_s верните другие вещи. Строка 'Infinity' не является чем-то особенным. Это не число с плавающей точкой литерал как -1.23 или 9.999999999999995e+39.


Вы можете посмотреть на это с противоположной стороны - большинство операций с плавающей запятой, когда #to_s -эд, возвращают строку, представляющую их буквальная форма. Это просто счастливое совпадение, что вам также нужно #to_f вернуть их. Float::INFINITY не возвращает строковую версию формы literal , поскольку она не имеет формы literal .


Вот и все. Хотя я думаю, что была упущена прекрасная возможность сделать «литералом бесконечности», они, вероятно, избавили себя от головной боли, не добавив символ юникода в качестве требования иметь полную грамматику языка.

Может Вы думаете о каком-либо языке программирования, который имеет буквальный синтаксис для бесконечности?

1 голос
/ 01 апреля 2020

Чтобы ответить на вторую часть вашего вопроса (на первую часть блестяще ответил @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. Если это хорошая идея или нет, это довольно сложный топи c, и в некотором смысле это зависит от вашего проекта и от того, как вы используете строки в своем коде.


Просто для сравнения, в ruby 2.5.3 на Windows (а не рельсы, таким образом 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)
0 голосов
/ 01 апреля 2020

Есть ли способ преобразовать строку в Float :: INFINITY или -Float :: INFINITY с помощью Ruby?

Вы можете использовать Object#const_get за это:

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