【问题标题】:Ruby output numbers to 2 decimal placesRuby 输出数字到小数点后 2 位
【发布时间】:2016-10-28 06:21:55
【问题描述】:

我无法将我的 ruby​​ 对象序列化为 json,更具体地说是数字的格式。 我编写了一个 rspec 测试来更准确地说明我的问题。

expected = '{ "foo": 1.00, "bar": 4.50, "abc": 0.00, "xyz": 1.23 }'

it 'serializes as expected' do
  my_hash = { "foo": 1, "bar": 4.5, "abc": 0, "xyz": 1.23}
  expect(my_to_json_method(my_hash)).to eq expected
end 

这是我遇到麻烦的情况。我可以使用 sprintf 但如何获得如上例所示的字符串输出?

【问题讨论】:

  • 请试试这个,test = { "foo": (sprintf "%.2f","1.0"), "bar": (sprintf "%.2f","4.50"), "abc": (sprintf "%.2f","0") }
  • 这些值的输出是字符串。所以格式化有效,但我希望结果不是结果中引用的字符串。
  • @mcbain83 你想存储货币价值吗?
  • 是的,那是正确的.. 我不确定这种行为在 json 字符串中是否那么重要.. 但我仍然想看看现在这是否可能。
  • 警告:顾名思义,浮点数没有固定的精度。如果您想要精确的值,这对金钱尤其很重要,请改用BigDecimal请勿使用浮点数进行货币计算。浮点数是一个值的精确近似,而不是一个值的精确表示。如果您使用 JSON 进行传输,我强烈建议您使用一个足够小的单位,这样您就不需要分数了,例如美分与美元。

标签: json ruby number-formatting


【解决方案1】:

首先,您不应该使用浮点数来表示货币价值。因此,让我们使用更合适的类型:(还有 Ruby Money gem)

require 'bigdecimal'

my_hash = {
  foo: BigDecimal.new('1.00'),
  bar: BigDecimal.new('4.50'),
  abc: BigDecimal.new('0.00'),
  xyz: BigDecimal.new('1.23')
}

有多种表示货币价值的选项。以下所有 JSON 字符串根据 JSON 规范都是有效的,并且在解析时都需要特殊处理。由您选择最合适的。

注意:我正在实现一个自定义 to_json 方法,以使用 Ruby 的默认 JSON 库将 BigDecimal 实例转换为 JSON。这仅用于演示目的,您通常不应修补核心(或 stdlib)类。


1。具有固定精度的数字

这是你要求的。请注意,默认情况下,许多 JSON 库会将这些数字解析为浮点值。

class BigDecimal
  def to_json(*)
    '%.2f' % self
  end
end
puts my_hash.to_json

输出:

{"foo":1.00,"bar":4.50,"abc":0.00,"xyz":1.23}

2。数字作为字符串

这将适用于所有 JSON 库,但将数字存储为字符串对我来说似乎不太合适。

class BigDecimal
  def to_json(*)
    '"%.2f"' % self
  end
end
puts my_hash.to_json

输出:

{"foo":"1.00","bar":"4.50","abc":"0.00","xyz":"1.23"}

3。整数形式的数字

您只需将 美分 输出为整数,而不是将货币值表示为小数。这是我通常做的。

class BigDecimal
  def to_json(*)
    (self * 100).to_i.to_s
  end
end
puts my_hash.to_json

输出:

{"foo":100,"bar":450,"abc":0,"xyz":123}

【讨论】:

  • 谢谢!是的,我正在使用金钱宝石。我想避免“用什么类型来赚钱”的讨论,因为我想专注于格式问题。我想你已经回答了..
  • 这是一个很好的答案,但我最好建议预先添加自定义模块和/或使用改进而不是猴子补丁核心库。
  • @mcbain83 在这种情况下,我会使用{"foo":{"cents":100,"currency":"USD"}}
  • @mudasobwa 因此是黄色注释 ;)
  • @Stefan 假设我无法更改预期的输出。您还有什么建议使用金钱宝石的吗?所有格式选项似乎都输出字符串。
【解决方案2】:

用户 Sprintf

sprintf('%.2f', 5.5)

然后简单地作为 ERB 模板插入到您的 JSON 中。

【讨论】:

  • humm erb 可能会起作用.. 模板会是什么样子?感觉有一大堆条件可能会有点乱……我会试着做一个,看看我能不能发布结果……
【解决方案3】:

您可以使用sprintf,并且可以通过提及%.(number)f 来取任意多的小数点。

例如:两位小数,%.2f

这是一个真正的实现,

2.2.2 :019 > test = { "foo": (sprintf "%.2f","1.11"), "bar": (sprintf "%.2f","4.55"), "abc": (sprintf "%.2f","0.2") }

 => {:foo=>"1.11", :bar=>"4.55", :abc=>"0.20"}

Here is the reference

【讨论】:

  • 如何输出相同的值不是作为字符串但作为数字值?
  • test = { "foo": (sprintf "%.2f","1.11").to_f, "bar": (sprintf "%.2f","4.55").to_f, "abc": (sprintf "%.2f","0.2").to_f }
  • 当您在“1.30”结果格式的字符串上使用 .to_f 时,您会得到 1.3,这又回到了原始问题。
  • 看看我修改后的问题。我把它写成 rspec 测试,以便更准确地满足我的要求。
  • 随着每次编辑,这段代码变得越来越离谱。这与 Ruby 的真正意义完全相反:不要重复自己是一个驱动原则,而这段代码的重复性荒谬可笑。
【解决方案4】:
puts '{' << my_hash.map { |k, v| %Q|"#{k}": #{"%.2f" % v}| }.join(', ') << '}'
#⇒ {"foo": 1.00, "bar": 4.50, "abc": 0.00, "xyz": 1.23}

【讨论】:

    猜你喜欢
    • 2015-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多