预期为整数的不需要的浮点数

Unwanted float expected to be integer

我正在尝试解决一个问题:

Your task is to write a simple function that takes a number of meters, and outputs it using metric prefixes.For this exercise we just want units bigger than a meter, from meters up to yottameters, excluding decameters and hectometers.All values passed in will be positive integers Examples

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])/,'')
end

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

我认为你的问题是你乘以 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])/,'')
end

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

这至少给了错误的解决方案。我不保证它是所有问题的正确解决方案。

我在这里将散列放入它自己的函数中,因为它看起来像是静态的。我在评论中提到的其他事情也是如此。

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

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

问题背后的原因是:Float 可以存储非常大的整数。但是,对于大于某个限制的整数,Float 不能 精确地存储它们 。对于 [IEEE-754 双精度] 浮点数,该限制为 253.

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

#!/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

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

FWIW 这一行会将 ruby 的鸭子输入更改为浮动。 (请注意,您将 10.0 作为浮点数引入。)

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

处理大数时,最好使用内置的BigDecimal class。更清晰,更不容易出错,尽管这绝对不是防错代码。

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
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"