5.8. Using BigDecimal
The bigdecimal standard library enables us to work with large numbers of significant digits in fractional numbers. In effect, it stores numbers as arrays of digits rather than converting to a binary floating point representation. This allows arbitrary precision, though of course at the cost of speed.
To motivate ourselves, look at the following simple piece of code using floating point numbers:
if (3.2 - 2.0) == 1.2 puts "equal" else puts "not equal" # prints "not equal"! end
This is the sort of situation that BigDecimal helps with. However, note that with infinitely repeating decimals, we still will have problems. For yet another approach, see the upcoming section 5.9 "Working with Rational Values."
A BigDecimal is initialized with a string. (A Float would not suffice because the error would creep in before we could construct the BigDecimal object.) The method BigDecimal is equivalent to BigDecimal.new; this is another special case where a method name starts with a capital letter. The usual mathematical operations such as + and * are supported. Note that the to_s method can take a parameter to specify its format. For more details, see the ruby-doc.org site.
require 'bigdecimal' x = BigDecimal("3.2") y = BigDecimal("2.0") z = BigDecimal("1.2") if (x - y) == z puts "equal" # prints "equal"! else puts "not equal" end a = x*y*z a.to_s # "0.768E1" (default: engineering notation) a.to_s("F") # "7.68" (ordinary floating point)
We can specify the number of significant digits if we want. The precs method retrieves this information as an array of two numbers: the number of bytes used and the maximum number of significant digits.
x = BigDecimal("1.234",10) y = BigDecimal("1.234",15) x.precs # [8, 16] y.precs # [8, 20]
The bytes currently used may be less than the maximum. The maximum may also be greater than what you requested (because BigDecimal TRies to optimize its internal storage).
The common operations (addition, subtraction, multiplication, and division) have counterparts that take a number of digits as an extra parameter. If the resulting significant digits are more than that parameter specifies, the result will be rounded to that number of digits.
a = BigDecimal("1.23456") b = BigDecimal("2.45678") # In these comments, "BigDecimal:objectid" is omitted c = a+b # <'0.369134E1',12(20)> c2 = a.add(b,4) # <'0.3691E1',8(20)> d = a-b # <'-0.122222E1',12(20)> d2 = a.sub(b,4) # <'-0.1222E1',8(20)> e = a*b # <'0.3033042316 8E1',16(36)> e2 = a.mult(b,4) # <'0.3033E1',8(36)> f = a/b # <'0.5025114173 8372992290 7221E0',24(32)> f2 = a.div(b,4) # <'0.5025E0',4(16)>
The BigDecimal class defines many other functions such as floor, abs, and others. There are operators such as % and ** as you would expect, along with relational operators such as <). The == is not intelligent enough to round off its operands; that is still the programmer's responsibility.
The BigMath module defines constants E and PI to arbitrary precision. (They are really methods, not constants.) It also defines functions such as sin, cos, exp, and others, all taking a digits parameter.
The following sublibraries are all made to work with BigDecimal.
These sublibraries are not documented in this chapter. For more information, consult the ruby-doc.org site or any detailed reference.