不需要的浮点数应该是整数

时间:2015-08-21 23:47:15

标签: ruby integer floating-point-precision

我正在尝试解决问题:

  

你的任务是写一个需要几米的简单函数,   并使用公制前缀输出它。对于我们想要的练习   大于一米的单位,从米到米,不包括   十进制和百米。传入的所有值都是正数   整数   实例

meters(51500)
# returns "51.5km"

meters(5000000)
# returns "5Mm"

我的代码:

def meters(x)
  map_prefix={ 24=>'Y', 21=> 'Z', 18=> 'E', 15=> 'P', 12=> 'T', 9=>'G', 6=>'M', 3=>'k',0=>'' }
  digits=(x.to_i.to_s.size-1)/3  
  division=x/(10.0**(3*digits))
  "#{division}#{map_prefix[3*digits]}m".sub(/\.0([^\d])/,'\1')
end

它不适用于meters(56*10**24) #->expected 56Ym ,instead got 56.000000000004Ym,但适用于较大的数字,例如meters(88*10**24) #->88Ym。代码通过了50个测试中的49个,有人可以帮我找到错误吗?

5 个答案:

答案 0 :(得分:1)

破解代码以使其正常工作的最简单方法似乎是避免使用浮点数,例如:

#!/usr/bin/env ruby

def meters(x)
  map_prefix={ 24=>'Y', 21=> 'Z', 18=> 'E', 15=> 'P', 12=> 'T', 9=>'G', 6=>'M', 3=>'k',0$
  map_prefix.default = 'Y'
  digits = [((x.to_s.size-1)/3)*3, 24].min
  division = x.to_s.insert(-digits - 1, '.')
  division.sub!(/0+\z/, '')
  division.sub!(/\.\z/, '')
  "#{division}#{map_prefix[digits]}m"
end

puts meters(51500)
puts meters(5000000)
puts meters(5001)
puts meters(88*10**24)
puts meters(88*10**24 + 1)
puts meters(100)
puts meters(88*10**27)
puts meters(88*10**27 + 1)

结果如:

 ./ruby.rb
51.5km
5Mm
5.001km
88Ym
88.000000000000000000000001Ym
100m
88000Ym
88000.000000000000000000000001Ym

更严重的是,你需要避免任何字符串(根本不应该转换为字符串)。 你需要任意精度,所以浮动根本不是一个选择。

答案 1 :(得分:0)

我认为您的问题是您乘以10.0,但您只想处理整数。

以下是你想要的东西。 (我也做了几次风格改变)。

def meters(x)
  digits=(x.to_i.to_s.size-1)/3
  prefix = prefixes[3*digits]
  value = x / (10 ** (3 * digits))
  "#{value}#{prefix}m".sub(/\.0([^\d])/,'\1')
end

def prefixes
  {
    24 => 'Y',
    21 => 'Z',
    18 => 'E',
    15 => 'P',
    12 => 'T',
    9 => 'G',
    6 => 'M',
    3 => 'k',
    0 => ''
  }
end

这至少可以为错误的解决方案提供正确的解决方案。我不能保证它是所有事情的正确解决方案。

我在这里将哈希变成了自己的函数,因为它似乎是静态的。我在评论中提到的其他事情也是如此。

答案 2 :(得分:0)

您可以使用Float#round将数字四舍五入到某些数字。对于这个问题,3应该没问题。

"#{division.round(3)}#{map_prefix[3*digits]}m".sub(/\.0([^\d])/,'\1')
#          ^^^^^^^^^

问题背后的原因是:Float可以存储非常大的整数。但是,对于大于某个限制的整数,Float无法存储精确。对于[IEEE-754双精度]浮点,该限制为2 53

答案 3 :(得分:0)

FWIW这条线将改变红宝石的鸭子打字漂浮。 (注意你将 10.0 作为浮动引入。)

division=x/(10.0**(3*digits))

处理大数字时,最好使用内置的BigDecimal类。虽然这绝对不是防错代码,但更清晰,更不容易出错。

require 'bigdecimal'

def meters(x)
  b = BigDecimal.new(x).split
  "#{b[1]}#{prefix[b[3] - b[1].length]}m"
end

def prefix
  {
    24 =>'Y', 21 => 'Z', 18 => 'E', 15 => 'P',
    12 => 'T', 9 =>'G',  6 =>'M', 3 =>'k',0 =>''
  }
end

答案 4 :(得分:0)

UNITS = " kMGTPEZY"

def meters(x)
  sx     = x.to_s
  pwr    = sx.size - 1
  return sx if pwr < 3
  pfx_sz = (pwr < 24) ? (1 + pwr % 3) : pwr - 23
  sxs    = sx.reverse.to_i.to_s.reverse
  sxs    = sxs.ljust([sxs.size, pfx_sz].max, '0')
  pfx    = sxs[0, pfx_sz]
  pfx    << '.' if (pfx.size < sxs.size)  
  "#{ pfx }#{ sxs[pfx_sz..-1] }#{ UNITS[[pwr/3, 8].min] }"
end

meters 3             #=> "3" 
meters 100           #=> "100" 
meters 4000          #=> "4k" 
meters 5001          #=> "5.001k" 
meters 51500         #=> "51.5k" 
meters 5000000       #=> "5M" 
meters 88*10**24     #=> "88Y" 
meters 88*10**24 + 1 #=> "88.000000000000000000000001Y" 
meters 88*10**27     #=> "88000Y" 
meters 88*10**27 + 1 #=> "88000.000000000000000000000001Y"