CI coveralls

Provides the fixed-point decimal type FixedDecimal allowing for exact representations of decimal numbers. These numbers are useful in financial calculations where interactions between decimal numbers are required to be exact.

This library defines the type FixedDecimal{T <: Integer, f} as a subtype of Real. The parameter T is the underlying machine representation and f is the number of decimal places which can be stored.

For example, FixedDecimal{Int8, 2} allows you to a decimal number with up to 2 fractional digits. All FixedDecimal{Int8, 2} numbers x must satisfy

-1.28 = -128/10² ≤ x ≤ 127/10² = 1.27

because the range of Int8 is from -128 to 127.

In general FixedDecimal{T <: Integer, f} numbers y must satisfy:

typemin(T)/10ᶠ ≤ y ≤ typemax(T)/10ᶠ


julia> using FixedPointDecimals

julia> 2.2 / 10

julia> FixedDecimal{Int,2}(2.2) / 10

julia> 0.1 + 0.2

julia> FixedDecimal{Int,1}(0.1) + FixedDecimal{Int,1}(0.2)

Arithmetic details: Overflow and checked math

NOTE: This section applies to FixedPointDecimals v0.5+.

By default, all arithmetic operations on FixedDecimals, except division, will silently overflow following the standard behavior for bit integer types in Julia. For example:

julia> FixedDecimal{Int8,2}(1.0) + FixedDecimal{Int8,2}(1.0)

julia> -FixedDecimal{Int8,2}(-1.28)  # negative typemin wraps to typemin again

julia> abs(FixedDecimal{Int8,2}(-1.28))  # negative typemin wraps to typemin again

Note that division on FixedDecimals will throw OverflowErrors on overflow, and will not wrap. This decision may be reevaluated in a future breaking version change release of FixedDecimals. Please keep this in mind.

In most applications dealing with FixedDecimals, you will likely want to use the checked arithmetic operations instead. These operations will throw an OverflowError on overflow or underflow, rather than silently wrapping. For example:

julia> Base.checked_mul(FixedDecimal{Int8,2}(1.2), FixedDecimal{Int8,2}(1.2))
ERROR: OverflowError: 1.20 * 1.20 overflowed for type FixedDecimal{Int8, 2}

julia> Base.checked_add(FixedDecimal{Int8,2}(1.2), 1)
ERROR: OverflowError: 1.20 + 1.00 overflowed for type FixedDecimal{Int8, 2}

julia> Base.checked_div(Int8(1), FixedDecimal{Int8,2}(0.5))
ERROR: OverflowError: 1.00 ÷ 0.50 overflowed for type FixedDecimal{Int8, 2}

Checked division: Note that checked_div performs truncating, integer division. Julia Base does not provide a function to perform checked decimal division (/), so we provide one in this package, FixedPointDecimals.checked_rdiv. However, as noted above, the default division arithmetic operators will throw on overflow anyway.

Here are all the checked arithmetic operations supported by FixedDecimals:

  • Base.checked_add(x,y)
  • Base.checked_sub(x,y)
  • Base.checked_mul(x,y)
  • Base.checked_div(x,y)
  • FixedPointDecimals.checked_rdiv(x,y)
  • Base.checked_cld(x,y)
  • Base.checked_fld(x,y)
  • Base.checked_rem(x,y)
  • Base.checked_mod(x,y)
  • Base.checked_neg(x)
  • Base.checked_abs(x)

Conversions, Promotions, and Inexact Errors.

Note that arithmetic operations will promote all arguments to the same FixedDecimal type before performing the operation. If you are promoting a non-FixedDecimal number to a FixedDecimal, there is always a chance that the Number will not fit in the FD type. In that case, the conversion will throw an exception. Here are some examples:

julia> FixedDecimal{Int8,2}(2)  # 200 doesn't fit in Int8
ERROR: InexactError: convert(FixedDecimal{Int8, 2}, 2)

julia> FixedDecimal{Int8,2}(1) + 2  # Same here: 2 is promoted to FD{Int8,2}(2)
ERROR: InexactError: convert(FixedDecimal{Int8, 2}, 2)

julia> FixedDecimal{Int8,2}(1) + FixedDecimal{Int8,1}(2)  # Promote to the higher-precision type again throws.
ERROR: InexactError: convert(FixedDecimal{Int8, 2}, 2.0)