-1

I'm writing an application in swift 4 and I do not want to round my double value every time I create a double. By default, I wish to make it 2 decimal place.

The double values will be all for currency display/saving purposes

E.g. USD 10.00, instead, it is saving like: 10.0

I'm searching for something to override the Double function.

Thank you for your help in advance

  • 3
    There's no need to round every double. Just use a proper `NumberFormatter` whenever you needs to show a double to a user. – rmaddy Oct 31 '18 at 03:08
  • Possible duplicate of [Rounding a double value to x number of decimal places in swift](https://stackoverflow.com/questions/27338573/rounding-a-double-value-to-x-number-of-decimal-places-in-swift) – Smartcat Oct 31 '18 at 05:03
  • Are you using these two decimal place numbers to represent currency values? – CRD Oct 31 '18 at 05:34
  • @CRD, yes exactly. Its an application related to currency only. So, instead of handing the rounding function every time, I wish to be able to do it by default. – Jeff Yeung Sam Wai Oct 31 '18 at 06:19
  • @Smartcat, I saw this post before and that's not what I was looking for, unfortunately – Jeff Yeung Sam Wai Oct 31 '18 at 06:21
  • @JeffYeungSamWai I've updated my answer according to your needs. I hope it helps you :) – Ayazmon Oct 31 '18 at 11:16
  • That's just not how `Double` works. See https://stackoverflow.com/questions/588004/is-floating-point-math-broken – Alexander Oct 31 '18 at 16:10
  • @JeffYeungSamWai In essence, double doesn't store "digits" (decimal digits, to be clear), in the sense of the way you and I think of decimals. It stores fractional parts as some sum of `1/2`, `1/4`, `1/8`, `1/16`, ... `1/(2^56)`. For example, `1.825` gets encoded as `1 + 1/2 + 1/4 + 1/8`. Any numbers that aren't precises expressible as a sum of powers of 2, are rounded off to the nearest available number that can. – Alexander Oct 31 '18 at 16:14
  • @Alexander. Yeps, Thank you for the information and I understand what you are trying to say, but it doesn't help me in what I'm trying to do. – Jeff Yeung Sam Wai Nov 01 '18 at 07:00
  • @JeffYeungSamWai I guess it's just trying to say "you can't do, what you're trying to do". Instead, us a CurrencyFormatter to convert your double to string, enforcing that it only prints 2 digits, and whatever other requirements you have – Alexander Nov 01 '18 at 17:53
  • @Alexander, Yes! I have decided to go with the currencyFormatter, I have created a static function for which I call it everywhere and whenever I need it. Thanks again – Jeff Yeung Sam Wai Nov 02 '18 at 01:39

2 Answers2

2

Its an application related to currency only. So, instead of handing the rounding function every time, I wish to be able to do it by default.

It is best to avoid Float & Double for currency applications. These types are based on binary floating point and are not precise with decimal fractions.

E.g. take 34 cents/pence/whatever in a currency where 100 of these make the dollar/pound/whatever then in you might use the decimal 0.34 to represent this, or 3/10 + 4/100 as the sum of 1/(10^n) fractions. Now binary floating-point (and fixed-point) uses sums of 1/(2^n) fractions. So 34/100 = 1/4 (25/100) + 9/100 = 1/4 + 1/16 + 275/10000 = 1/4 + 1/16 + 1/64 + ... (this sum could be finite or infinite depending on the decimal fraction)

Note that given this binary base your requirement to have two decimal places is not strictly possible, as regardless of how many binary places you limit yourself to some of your decimal factions will be imprecise.

So what is the solution? Integers (or fixed-point numbers, see below).

If you only need to support decimal currencies, i.e. those where the small unit is 1/100 of the larger one as in dollars/cents, pounds/pence) then you can simply use large enough integers and keep your amounts in the small unit. E.g store $12.34 as 1234 cents. If you need greater precision (say you're doing share prices etc.) you might store your values as some (decimal) factions of the small unit, say 1/100 of a cent. In this way all calculations use precise integer arithmetic operations. For string conversions you need to add/remove the currency point and then do integer conversions, or use remainder/division to split your stored value into its components and format each one.

If you need to support non-decimal currencies, or you need more range than storing as (some fraction of) the smaller unit will allow in your largest integer type, then you can store the large & small units as two integer values and handle carry operations between them yourself.

You can wrap the operations up as a value (struct) type in Swift.

Some language support decimal fixed point arithmetic, or provide libraries which do, which is similar to what we've just described; there might be a library for Swift out there if you look. (See for example Python's decimal, that link also covers the points explained above.)

If you wish to stick with available types then look at using Decimal which is a decimal floating point type which provides 38 decimal digits of precision. It is not designed specifically for currency but you can use NSDecimalRound to keep numbers to the required number of decimal places (and it even supports bankers rounding); to address your specific requirement to do this automatically you would have to wrap/extend and implement your own operators which combined the standard ones with rounding to the required number of places.

Hope this helps more than it confuses!

CRD
  • 52,522
  • 5
  • 70
  • 86
0

I can suggest two solutions to your question. But I'm not sure if they'll satisfy your needs. First is overriding didSet of your Double currency value variable like this:

var currencyValue: Double = 0.0 {
   didSet {
        // Do the rounding
        let divisor = pow(10.0, 2) // 2 is the place number

        currencyValue = (currencyValue * divisor).rounded() / divisor
   }
}

Second is using extensions to create a new init function for Double which does the rounding immediately.

extension Double {

    init(value: Double, roundTo places: Double) {
        let divisor = pow(10.0, places)

        self = (value * divisor).rounded() / divisor
    }
}
// Use the extension initialization
Double(value: 10.22, roundTo: 2)

Although these can be used as solutions I must agree with @rmaddy. Using NumberFormatter would allow you to format currencies much easily and locally. Hope this helps.

Edit:

So to achieve what you are actually looking for we need to do some little adjustments. I'll update override method:

var currencyValue: String = "0.00" {
   get {
        // Convert String value to Double
        let doubleValue = Double(currencyValue)

        // Do the rounding
        let divisor = pow(10.0, 2) // 2 is the place number

        doubleValue = (doubleValue * divisor).rounded() / divisor

        // Format the number
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = NumberFormatter.Style.decimal
        numberFormatter.minimumFractionDigits = 2 // We want at least 2 decimal places
        if let formattedString = numberFormatter.string(from: NSNumber(doubleValue)) {
             return formattedString
        }

        return currencyValue
   }
} 

You can use it like this:

var double = 10.2
currencyValue = "\(double)" // Setting the double value as String
print(currencyValue) // will print 10.20
Ayazmon
  • 1,360
  • 8
  • 17