如何在Ruby中将小数舍入到第一个有效数字

时间:2019-07-19 15:36:13

标签: ruby-on-rails ruby algorithm decimal

我正在尝试解决与个人项目有关的任务的极端情况。

这是确定服务的单价,由total_amountcost组成。

示例包括:

# 1
unit_price = 300 / 1000 # = 0.3

# 2
unit_price = 600 / 800 # = 0.75

# 3
unit_price = 500 / 1600 # = 0.3125

对于1和2,单价可以保持不变。对于3,四舍五入到小数点后2位就足够了,例如(500 / 1600).round(2)

浮点数变长时会出现问题:

# 4
unit_price = 400 / 56000 # = 0.007142857142857143

显而易见的是,浮标相当长。在这种情况下,目标是舍入到第一个有效数字。

我已经考虑过使用正则表达式来匹配第一个非零小数,或者找到第二个部分的长度并应用一些逻辑:

  • unit_price.match ~= /[^.0]/
  • unit_price.to_s.split('.').last.size

任何帮助都将受到欢迎

3 个答案:

答案 0 :(得分:3)

人们应该使用BigDecimal进行这种计算。

require 'bigdecimal'

bd = BigDecimal((400.0 / 56000).to_s)
#⇒ 0.7142857142857143e-2
bd.exponent
#⇒ -2

示例:

[10_000.0 / 1_000, 300.0 / 1_000, 600.0 / 800,
                   500.0 / 1_600, 400.0 / 56_000].
  map { |bd| BigDecimal(bd.to_s) }.
  map do |bd|
    additional = bd.exponent >= 0 ? 0 : bd.exponent + 1 
    bd.round(2 - additional) # THIS
  end.
  map(&:to_f)
#⇒ [10.0, 0.3, 0.75, 0.31, 0.007]

答案 1 :(得分:1)

您可以使用正则表达式检测零字符串的长度。有点丑陋,但是可以用:

def significant_round(number, places)
  match = number.to_s.match(/\.(0+)/)
  return number unless match
  zeros = number.to_s.match(/\.(0+)/)[1].size
  number.round(zeros+places)
end  

pry(main)> significant_round(3.14, 1)
=> 3.14
pry(main)> significant_round(3.00014, 1)
=> 3.0001

答案 2 :(得分:0)

def my_round(f)
  int = f.to_i
  f -= int
  coeff, exp = ("%e" % f).split('e')
  "#{coeff.to_f.round}e#{exp}".to_f + int
end

my_round(0.3125)
  #=> 0.3 
my_round(-0.3125)
  #=> -0.3 
my_round(0.0003625)
  #=> 0.0004 
my_round(-0.0003625)
  #=> -0.0004 
my_round(42.0031)
  #=> 42.003 
my_round(-42.0031)
  #=> -42.003 

步骤如下。

f = -42.0031
int = f.to_i
  #=> -42 
f -= int
  #=> -0.0031000000000034333 
s = "%e" % f
  #=> "-3.100000e-03" 
coeff, exp = s.split('e')
  #=> ["-3.100000", "-03"] 
c = coeff.to_f.round
  #=> -3 
d = "#{c}e#{exp}"
  #=> "-3e-03" 
e = d.to_f
  #=> -0.003 
e + int 
  #=> -42.003 

要改为只舍入四舍五入后的最高有效位,请将方法更改为以下内容。

def my_round(f)
  coeff, exp = ("%e" % f).split('e')
  "#{coeff.to_f.round}e#{exp}".to_f
end

如果f <= 0返回与以前的方法相同的结果。这是f > 0时的示例:

my_round(-42.0031)
  #=> -40.0