How to round Decimals to the First Significant Figure in Ruby
One should use BigDecimal
for this kind of computation.
require 'bigdecimal'
bd = BigDecimal((400.0 / 56000).to_s)
#⇒ 0.7142857142857143e-2
bd.exponent
#⇒ -2
Example:
[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]
Calculate number of significant digits
I understand the numbers are non-negative. One can do the following:
Code
def floor_it(f, sig_digits=5)
pow = sprintf("%e", f)[-3..-1].to_i
(f *= 10**(-pow)).floor(sig_digits-1) * 10**(pow)
end
Examples
floor_it 0.007425742694 #=> 0.0074257
floor_it 3852.574257425742694 #=> 3852.5
Explanation
For
f = 385.74957425742694
sig_digits = 5
the steps are as follows. First express the number in scientific notation (as a string). See Kernel#sprintf.
a = sprintf("%e", f)
#=> "3.857496e+02"
We wish to extract the last two digits.
b = a[-3..-1]
#=> "02"
Convert that to an integer.
pow = b.to_i
#=> 2
Shift f
's decimal point so there is one non-zero digit to the left of the decimal point.
f *= 10**(-pow)
#=> 3.8574957425742697
Use the method Float#floor to obtain the desired digits.
d = f.floor(sig_digits-1)
#=> 3.8574957425742697.floor(4)
#=> 3.8574
Lastly, shift the decimal point back to its initial position.
d * 10**(pow)
#=> 385.74
Ruby round Float up or down to specific decimal significant figure
In Ruby 2.4+, the Float#float
& Float#ceil
methods take a ndigits
argument:
1.33333333.ceil(2) -> 1.34
1.33333333.floor(3) -> 1.333
However, check out this behavior with those STD lib methods:
# In Ruby 2.4.2:
0.07.ceil(2) -> 0.08
1.1.ceil(2) -> 1.11
Not OK in my book.
For older Ruby versions or if you want to get better results than the STB lib gives, you will need to write your own methods. There are a few different blog posts out there, and I'll explain why they're not consistently correct later, but here are some methods that should work correctly every time:
require 'bigdecimal'
class Float
def ceil2(exp = 0)
BigDecimal(self.to_s).ceil(exp).to_f
end
def floor2(exp = 0)
BigDecimal(self.to_s).floor(exp).to_f
end
end
Now for more on why the following are incorrect:
def ceil_to(x)
(self * 10**x).ceil.to_f / 10**x
end
def floor_to(x)
(self * 10**x).floor.to_f / 10**x
end
# These methods also produce bad results for the examples shown above
0.07.ceil(2) -> 0.08
1.1.ceil(2) -> 1.11
I won't go into the details about what is happening (you can find that
here or here), but floating point arithmetic can be messy and rounding errors do occur.
Ruby: Rounding float in Ruby
When displaying, you can use (for example)
>> '%.2f' % 2.3465
=> "2.35"
If you want to store it rounded, you can use
>> (2.3465*100).round / 100.0
=> 2.35
How do you round a float to 2 decimal places in JRuby?
Float#round can take a parameter in Ruby 1.9, not in Ruby 1.8. JRuby defaults to 1.8, but it is capable of running in 1.9 mode.
Related Topics
Implementing a Synchronization Barrier in Ruby
How to Set Environment Variable Using Chef
How to Use Escape Characters in Strings
How to Run All Ruby Scripts with Warnings
Ruby Gems Won't Load Even Though Installed
Grouping Consecutive Numbers in an Array
Exec': String Contains Null Byte (Argumenterror)
Could Not Find Rake-10.1.0 in Any of the Sources
Yielding in an Anonymous Block
How to Share State Between Scenarios Using Cucumber
Activerecords Select(:Id).Collect VS. Pluck(:Id) Methods: Why Is Pure Ar "Pluck" Slower
Semifixed: Missing 'Secret_Key_Base' for 'Production' Environment
Ruby on Rails: Search Form - Multiple Search Fields
How to Make Ruby's Restclient Gem Respect Content_Type on Post