Ruby on rails 具有不同选项的轨道圆角浮动

Ruby on rails 具有不同选项的轨道圆角浮动,ruby-on-rails,ruby,Ruby On Rails,Ruby,我有一个表单,用户在其中输入一个十进制值和一个带有四个选项的下拉列表:美元(.00),季度(.00、.25、.50、.75),一角(.10、.20、.30…90)和便士(.01、.02、.03…99)。还有一个选项可选择向上取整或向下取整 这些选项用于对用户输入的值进行四舍五入。我对Float类进行了修补,并将round\u添加到了\u quarter,效果很好: class Float def round_to_quarter (self * 4).round / 4.0 en

我有一个表单,用户在其中输入一个十进制值和一个带有四个选项的下拉列表:
美元(.00)
季度(.00、.25、.50、.75)
一角(.10、.20、.30…90)
便士(.01、.02、.03…99)
。还有一个选项可选择向上取整<代码>或向下取整<代码>

这些选项用于对用户输入的值进行四舍五入。我对
Float
类进行了修补,并将
round\u添加到了\u 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
如何舍入Dime(.10、.20、.30…90)和Penny(.01、.02、.03…99)选项的值并上下舍入


Ruby版本是2.2.3

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

天花板随上升而上升,地板随下降而下降

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

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

天花板随上升而上升,地板随下降而下降

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

四舍五入和四舍五入一样好用

def round_to_penny
  ((self * 100).round/100.0)
end 
然而,四舍五入到一角将减少到小数点后1位,因为您将四舍五入到小数点后1位。 显示值时,可以将其更改为小数点后2位

def round_to_dime
  ((self * 10).round/10.0)
end
但是,您可以使用“%.2f”:

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

四舍五入和四舍五入一样好用

def round_to_penny
  ((self * 100).round/100.0)
end 
然而,四舍五入到一角将减少到小数点后1位,因为您将四舍五入到小数点后1位。 显示值时,可以将其更改为小数点后2位

def round_to_dime
  ((self * 10).round/10.0)
end
但是,您可以使用“%.2f”:

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

以下是一种通用方法,可用于任何精度:

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

此代码首先将浮点转换为整数以确保精度。小心使用带有浮点数运算的
ceil
floor
——由于精度错误,您可能会得到奇数结果,例如
9.37*100=936.99999999999
。如果您对结果进行
floor
,则最终将四舍五入到9.36

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

此代码首先将浮点转换为整数以确保精度。小心使用带有浮点数运算的
ceil
floor
——由于精度错误,您可能会得到奇数结果,例如
9.37*100=936.99999999999
。如果你对结果进行
floor
,你最终会四舍五入到9.36

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

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
那么就这样使用吧

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

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

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
那么就这样使用吧

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

,至少使用
到_-dime
到_-dime将是
(self*10)。舍入/10.0
?,至少使用
到_-dime
到_-dime将是
(self*10)。舍入/10.0
和美元(.00)和美元(.00)的精度将每季度得到4,10表示一角硬币,100表示便士?默认值
1
为不舍入(便士)。使用
10
表示一角硬币,
25
表示四分之一硬币,
100
表示美元和美元(.00)?@Arif:您将要使用
100
。我已经更新了代码片段来澄清这一点,也许可以使用
BigDecimal
来避免浮点精度问题。这可以很容易地添加
integer_value=(BigDecimal.new(self)*100)。舍入
precision
将得到四分之一的精度,十分之一的精度和一百分之一的精度?默认
1
是不舍入(便士)。使用
10
表示一角硬币,
25
表示四分之一硬币,
100
表示美元和美元(.00)?@Arif:您将要使用
100
。我已经更新了代码片段来澄清这一点,也许可以使用
BigDecimal
来避免浮点精度问题。这可以很容易地添加
整数值=(BigDecimal.new(self)*100)。舍入