Rails Rounding浮动与不同的选项

时间:2017-05-30 07:31:43

标签: ruby-on-rails ruby

我有一个表单,用户输入一个十进制值,下拉四个选项:Quarter (.00, .25, .50, .75)Dime (.10, .20, .30 .. .90)Penny (.01, .02, .03 ... .99)UP。此外,您还可以选择轮次DOWNFloat

这些选项用于舍入用户输入的值。我修补了round_to_quarter课程,并添加了class Float def round_to_quarter (self * 4).round / 4.0 end def round_to_dime #TODO end def round_to_penny #TODO end def round_to_dollar #TODO end end 9.22.round_to_quarter #=> 9.25 正常工作:

func setupClient(){
    let configuration = ConfigData.sharedInstance

    if (configuration.sipLogin == SipLoginStatus.SipLoginStatusLoggedOut && configuration.sipUsername?.length == 0) && (configuration.acsLogin == ACSLoginStatus.ACSLoginStatusLoggedOut && configuration.acsUsername?.length == 0){
        return
    }

    //the app is broken here with the code "exc_bad_access(code=1,address=0x20)" and CSClient is a Class imported by a third party framework.
    CSClient.setLogLevel(CSLogLevel.Debug)
    ...
}

如何舍入Dime(.10,.20,.30 .. .90)和Penny(.01,。02,。03 ... .99)选项的值并向上和向下舍入?< / p>

Ruby版本是2.2.3

4 个答案:

答案 0 :(得分:4)

这是以任何精度执行此操作的通用方法:

class Float
  def round_currency(precision: 1, direction: :none)
    round_method = case direction
      when :none then :round
      when :up   then :ceil
      when :down then :floor
    end

    integer_value = (self * 100).round
    ((integer_value / precision.to_f).send(round_method) * precision / 100.0)
  end
end


# USAGE
9.37.round_currency(direction: :none, precision: 10)
# => 9.4

9.37.round_currency(direction: :up, precision: 25)
# => 9.5

9.37.round_currency(direction: :none)
# => 9.37

# Precision is defined in pennies: 10 dime, 25 quarter, 100 dollar. 1 penny is default

此代码首先将float转换为整数以确保准确性。请谨慎使用ceilfloor进行浮动数字运算 - 由于准确性错误,您可能会得到奇怪的结果,例如: 9.37 * 100 = 936.9999999999999。如果你floor结果,那么你最终将四舍五入为9.36

答案 1 :(得分:0)

我认为您可以尝试以下覆盖...

Ceil选择UP,Floor选择DOWN

class Float
  def ceil_to_quarter
    (self * 4).ceil / 4.0
  end

  def floor_to_quarter
    (self * 4).floor / 4.0
  end

  def ceil_to_dime
    (self * 10).ceil / 10.0
  end

  def floor_to_dime
    (self * 10).floor / 10.0
  end

  def ceil_to_penny
    (self * 10).ceil / 10.0
  end 

  def floor_to_penny
    (self * 100).floor / 100.0
  end 
end

答案 2 :(得分:0)

圆到便士应该像季度一样正常工作

def round_to_penny
  ((self * 100).round/100.0)
end 

然而,由于您要舍入到小数点后1位,因此舍入到对角线将减少到小数点后1位。 显示值时,您可以将其更改为2位小数。

def round_to_dime
  ((self * 10).round/10.0)
end

您可以使用&#39;%。2f&#39;但是:

'%.2f' % 9.25.round_to_dime => "9.30"

答案 3 :(得分:0)

处理这种情况的另一种方法:

require 'bigdecimal'
class Rounder
  DENOMS = {penny: 0.01, nickel: 0.05, dime: 0.1, quarter: 0.25, half_dollar: 0.5, dollar: 1.0}
  DENOMS.each do |denom,val|
    define_method("round_to_#{denom}") do |direction: :nearest|
        self.send(direction, self.send(:step_def, val))
    end     
  end
  def initialize(v)
    @v = BigDecimal(v.to_s)
    @enum_range = (@v.floor..@v.floor + 1)
  end

  def round_to(denom,direction: :nearest)
    check_denom(denom)
    if denom.is_a?(Numeric)
      self.send(direction, self.send(:step_def, denom))
    else
      self.public_send("round_to_#{denom}",direction: direction) 
    end    
  end


  private 
    def down(enum)
        enum.reverse_each.detect {|f| f <= @v }
    end
    def up(enum) 
        enum.detect {|f| f >= @v }
    end
    def nearest(enum)
      [up(enum),down(enum)].min_by {|n| (n - @v).abs}
    end
    def step_def(val)
      @enum_range.step(val)
    end  
    def check_denom(denom)
      if denom.is_a?(Numeric)
        raise ArgumentError, "Numeric denom must be greater than 0 and less than or equal to 1" if (denom > 1 || denom <= 0)
      elsif denom.respond_to?(:to_sym)
        raise ArgumentError, "expecting one of #{DENOMS.keys} got :#{denom.to_sym}" unless DENOMS.keys.include?(denom.to_sym)
      else
        raise ArgumentError,"expected Numeric, Symbol or String got #{denom.class}"
      end
    end    
end

这允许灵活地实现预定义的面额以及舍入到任何所需的精度。显然,通过缩小@enum_range可以对这一点进行优化以获得更长的精度。

您可以将其修补到Numeric以允许直接访问:

class Numeric
  def round_to(denom,direction: :nearest)
    Rounder.new(self).round_to(denom,direction: direction)
  end
end

然后使用

r = Rounder.new(9.22)
r.round_to_quarter
#=> 9.25
r.round_to_dime(direction: :up)
#=> 9.3
r.round_to(:nickel)
#=> 9.2
r.round_to(0.45, direction: :up)
#=> 9.45
r.round_to({})
#=> ArgumentError: expected Numeric, Symbol or String got Hash
r.round_to(:pound)
#=> ArgumentError: expecting one of [:penny, :nickel, :dime, :quarter,
#     :half_dollar, :dollar] got :pound
77.43.round_to(:quarter)
#=> 77.5
Rounder.new("123.0000001").round_to_half_dollar(direction: :up)
#=> 123.5
#Obviously a Fixnum is already precise but it does still work 
[:down,:up].each do |dir|
  puts "#{dir} => #{12.round_to(:quarter, direction: dir)}"
end
# down => 12.0
# up => 12.0