5

数値を100分の1に四捨五入し、結果を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ライブラリまたは別のJSONgemのいずれかを使用している可能性があります。

編集:以下の回答へのコメントに記載されているように、私は、具体的には数字のフラット配列ではなく、数字を含む任意のデータのすべてのJSONシリアル化に対する一般的な解決策を探しています。


この特定のケースでは、メソッドを書き直すことで上記の問題を修正できることに注意してください。

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

...しかし、これは、シリアル化中に精度レベルを強制するという一般的な要望を解決するものではありません。

4

4 に答える 4

3

ビルトインの JSON ライブラリはNumerics を呼び出さないため (おそらく速度のため)、Rails から ActiveSupport ライブラリを使用できます#as_json( Railsは必要ありません)。#to_json

呼び出し時にユーザー指定のオプションが渡された場合にのみ有効になるように、モンキーパッチを慎重に行います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 に答える
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 に答える
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 のデフォルトの配列シリアライザーまたは Float シリアライザーをオーバーライドする何かを書くことができますが、JSON は必要な精度またはフォーマット文字列を受け入れるものでそれを行います。


明確にするために、の使用はto_fシリアル化の前に発生します。これにより、結果の JSON 出力が肥大化したり、受信側で何らかの「魔法」が強制されたりすることはありません。float は転送され、float として逆シリアル化されます。

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

そして、それを Float の配列に適用する方法を見つけます。ただし、おそらく純粋な Ruby JSON を使用する必要があります。

于 2012-07-31T18:54:11.323 に答える
-2
  1. シリアル化中に精度を処理しないでください。
  2. おそらく、これらの数値に明示的にしていないセマンティクスがあります(おそらく、通貨の値ですか?これは、このセマンティクスを明示的にする必要のない、迅速で汚いスクリプトです)。
  3. プログラムで実際に精度が必要な場合に実装として浮動小数点数を使用することは、問題に対する悪いアプローチです。浮動小数点数は、その表現に固有の不正確さがあります(浮動小数点精度の問題)。代わりにBigDecimalを使用してください。
于 2012-07-31T19:06:48.267 に答える