Search code examples
rubyfloating-pointroundingfloorceil

Ruby round Float up or down to specific decimal significant figure


I'm using Ruby 2.3.1, and here's what I want to be able to do:

1.33333333.ceil(2) -> 1.34
1.33333333.floor(3) -> 1.333

The Float#round method allows me to round, but I need to be able to specify if I want to round up or down, similar to the #ceil and #floor methods, but with a parameter to specify how many decimal places to keep.


Solution

  • 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.