# RoundedDecimal

RoundedDecimal comes in two flavours; `RoundedDecimal<T: DecimalPlaces>`

and `DynamicRoundedDecimal`

. Different situations will require the use of one or the other.

`RoundedDecimal<T: DecimalPlaces>`

Swift decimals where the number of decimal places is explicitly part of the type. e.g. `RoundedDecimal<Places.five>`

can only operate with other `RoundedDecimal<Places.five>`

values. This is guaranteed at compile-time, but requires the developer to know upfront what level of precision is needed.

### Example:

```
// listedPrice == 2.59
let listedPrice: RoundedDecimal<Places.two> = "2.5872659"
// exchangePrice == 1.12345
let exchangeRate: RoundedDecimal<Places.five> = "1.1234528492"
let localPrice = listedPrice * exchangeRate
```

will result in the compilation failure:

```
binary operator '*' cannot be applied to operands of type 'RoundedDecimal<Places.two>' and 'RoundedDecimal<Places.five>'
let localPrice = listedPrice * exchangeRate
~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~
```

These situations can be handled, but the developer must explicitly decide upon the resulting precision before the multiplication will be allowed. See the documentation for `RoundedDecimal`

for more information.

`DynamicRoundedDecimal`

This type is useful when the number of decimal places needed can't be known at compile-time. For example, when dealing with arbitrary currencies decided at run-time which have a varying number of decimal places. USD which has 2 decimal places, but JPY that 0 decimal places. `DynamicRoundedDecimal`

is suitable in this scenario, as it the number of decimal places required is provided upon construction.

### Example:

```
// listedPrice == 2.59 after using a scale of 2 to represent USD
let listedPrice = DynamicRoundedDecimal(stringLiteral: "2.5872659", scale: 2)
// exchangePrice == 108.09364 after using a scale of 5
let exchangeRate = DynamicRoundedDecimal(stringLiteral: "108.0936412", scale: 5)
// localPrice = 279.96253 which uses the largest scale of either decimal, 5 in this case
let localPrice = listedPrice * exchangeRate
// appropriateLocalPrice = 280 after using a scale of 0 to represent JPY
let appropriateLocalPrice = localPrice.with(scale: 0)
```

## Installation

### Swift Package Manager

```
dependencies: [
.package(url: "https://github.com/SoftwareEngineerChris/RoundedDecimal.git", from: "2.2.0")
]
```

## Why

*TL/DR: We would like to guarantee a level of precision, or be explicit when changing the level of precision, of decimals moving through our system.*

When dealing with decimals we often want to know; deal with; or maintain a number of decimal places in the number we're representing.

For example, when dealing with money in a shopping application that serves the UK and US, we want to deal with numbers that have 2 decimal places. Especially when dealing with lots of these numbers and converting between USD and GBP.

If we have a product that costs *£2.50* to sell in the United Kingdom, and we wanted to sell it in the United States, we may want to use an exchange rate to calculate the local price. If the rate provided by our API is *0.81245 GBP per USD* (or **1.23084 USD per GBP*), using a simple multiplication, we may calculate the price for the item to be *$3.0771* in the United States.

We probably don't want to present *$3.0771* to our user as they don't normally deal in fractions of pennies. In our presentation layer we *may* format the value in the receipt as *$3.08*.

The user has decided to order *300* of these items. The calculation in our application doesn't necessarily know how the data is being presented, so deals with the item price of *$3.0771*. Therefore, *$3.0771* multiplied by *300* is *$923.13*.

In this case we may present a receipt to the user which looks like: `Special Product @ £3.08 x 300 = £923.13`

Except *300* multiplied by *$3.08* isn't *$923.13*. It's *$924.00*. The user is confused about the total presented to them, not realising that the calculation used a price of $3.0771 per item rather than the $3.08 presented.

What they should have seen on their receipt is either:
`Special Product @ $3.08 x 300 = $924.00`

or `Special Product @ $3.0771 x 300 = $923.13`

How this is handled should be a business decision, but we should be able to guarantee the result of that decision in our code. We can make this a compile-time requirement using `RoundedDecimal`

if the currencies used, or at least required decimal lengths, are static and known at compile-time. Or we can use `DynamicRoundedDecimal`

to handle this if we're dealing with currencies, or decimal lengths, chosen at run-time. See each type's documentation for how this can be handled.