5

考虑以下代码,该代码旨在将数字四舍五入到最接近的百分之一并将结果序列化为 JSON:

require 'json'
def round_to_nearest( value, precision )
  (value/precision).round * precision
end
a = [1.391332, 0.689993, 4.84678]
puts a.map{ |n| round_to_nearest(n,0.01) }.to_json
#=> [1.3900000000000001,0.6900000000000001,4.8500000000000005]

有没有办法使用 JSON 以特定级别的精度序列化所有数字

a.map{ ... }.to_json( numeric_decimals:2 )
#=> [1.39,0.69,4.85]

这可以使用 Ruby 内置的 JSON 库或其他 JSON gem。

编辑:正如在下面对答案的评论中所指出的,我正在寻找一个通用解决方案来处理所有包含数字的任意数据的 JSON 序列化,而不是具体的数字平面数组。


请注意,在这种特定情况下,可以通过重写方法来解决上述问题:

def round_to_nearest( value, precision )
  factor = 1/precision
  (value*factor).round.to_f / factor
end

...但这并不能解决在序列化过程中强制精度级别的普遍愿望。

4

4 回答 4

3

我只是使用 ruby​​ 的内置圆形方法对其进行预圆形:http ://www.ruby-doc.org/core-1.9.3/Float.html#method-i-round

a.map{ |n| n.round(2) }.to_json

这对我来说看起来很干净,而不是获取所有类型的自定义库并传入参数。

编辑评论:

我知道您可以通过积极支持做到这一点。

# If not using rails
require 'active_support/json'

class Float
  def as_json(options={})
    self.round(2)
  end
end

{ 'key' => [ [ 3.2342 ], ['hello', 34.1232983] ] }.to_json
# => {"key":[[3.23],["hello",34.12]]}

更精确的解决方案:更好的猴子补丁

于 2012-07-31T18:17:04.413 回答
3

由于内置的​​ JSON 库不调用#as_json#to_json在 Numerics 上(大概是为了速度),我们可以使用 Rails 的ActiveSupport库(不需要 Rails)。

我们巧妙地做了猴子补丁,这样它只有在调用时传递用户指定的选项时才会生效to_json

require 'active_support/json' # gem install activesupport

class Float
  def as_json(options={})
    if options[:decimals]
      value = round(options[:decimals])
      (i=value.to_i) == value ? i : value
    else
      super
    end
  end
end

data = { key: [ [ 2.991134, 2.998531 ], ['s', 34.127876] ] }
puts data.to_json             #=> {"key":[[2.991134,2.998531],["s",34.127876]]}
puts data.to_json(decimals:2) #=> {"key":[[2.99,3],["s",34.13]]}

如上一个示例所示,有一些额外的代码用于将整数值浮点数转换为纯整数,这样序列化就不会浪费字节输出3.00,而可以直接放入3(JSON 和 JS 中的值相同)。

于 2012-07-31T23:11:54.037 回答
0

因为您正在处理浮点,所以我倾向于说使用格式字符串转换为字符串,然后传递它们。他们可以让你很好地设置精度:

a = [1.391332, 0.689993, 4.84678]
a.map{ |f| '%.2f' % f }
=> ["1.39", "0.69", "4.85"]

如果您想要真正的浮点数,请将它们转换回来:

a.map{ |f| ('%.2f' % f).to_f }
=> [1.39, 0.69, 4.85]

从那时起,您可以编写一些东西来覆盖 JSON 的默认 Array 序列化程序或 Float 序列化程序,但是 JSON 会这样做,它可以接受您想要的精度或格式字符串。


只是为了澄清一下,使用to_f将在序列化之前发生,这不会使结果 JSON 输出膨胀或在接收端强制任何类型的“魔法”。浮点数将传输并反序列化为浮点数:

irb(main):001:0> require 'json'
true
irb(main):002:0> a = [1.391332, 0.689993, 4.84678]
[
    [0] 1.391332,
    [1] 0.689993,
    [2] 4.84678
]
irb(main):003:0> a.map{ |f| ('%.2f' % f).to_f }.to_json
"[1.39,0.69,4.85]"
irb(main):004:0> JSON.parse(a.map{ |f| ('%.2f' % f).to_f }.to_json)
[
    [0] 1.39,
    [1] 0.69,
    [2] 4.85
]

从以下内容开始:

class Float
  def to_json
    ('%.2f'%self).to_f
  end
end
(355.0/113).to_json # => 3.14

并弄清楚如何将其应用于浮点数组。不过,您可能需要使用纯 Ruby JSON。

于 2012-07-31T18:54:11.323 回答
-2
  1. 序列化过程中不应该处理精度;
  2. 可能你对这些数字有一些你没有明确表示的语义(也许是货币值?也许这是一个快速而肮脏的脚本,不需要明确表示这个语义)和
  3. 当您的程序真正需要精度时,使用浮点数作为您的实现是解决问题的不好方法。浮点数的表示具有固有的不准确性(浮点精度问题)。请改用 BigDecimal。
于 2012-07-31T19:06:48.267 回答