0

I want to format decimals to have four non-zero numbers after the final 0. For example, 0.001765 or 0.00004839 .

Any numbers above 1 would simply have two decimals. For example, 2.23 .

I am wondering if I can accomplish this using NumberFormatter?

vikzilla
  • 3,998
  • 6
  • 36
  • 57

3 Answers3

3

Use the maximumSignificantDigits property.

let nf = NumberFormatter()
nf.maximumSignificantDigits = 4
print(nf.string(for: 0.00000232323)!)
// prints 0.000002323
print(nf.string(for: 2.3)!)
// prints 2.3
Samah
  • 1,224
  • 2
  • 13
  • 15
  • 1
    No need to initialize a new `NSNumber` object. You can use `Formatter` method `string(for: Any)` instead of `from` – Leo Dabus Jan 23 '18 at 00:17
  • 1
    It was simply an example of what `maximumSignificantDigits` does. I've updated it. – Samah Jan 23 '18 at 00:23
  • @LeoDabus Fair point, though I would question what the use case actually was. – Samah Jan 23 '18 at 00:28
  • Thank you for providing a solution to part of the problem I was facing. This definitely solves the first case I described :) I will provide an answer with how I solved the entire case – vikzilla Jan 23 '18 at 01:58
2

You have two conditions. Use the minimumSignificantDigits and maximumSignificantDigits for the 4 digits and use maximumFractionDigits for the 2 places for values over 1.

extension FloatingPoint {
    func specialFormat() -> String {
        let fmt = NumberFormatter()
        fmt.numberStyle = .decimal
        if abs(self) >= 1 {
            // adjust as needed
            fmt.maximumFractionDigits = 2
        } else {
            fmt.minimumSignificantDigits = 4
            fmt.maximumSignificantDigits = 4
        }

        return fmt.string(for: self)!
    }
}

print(0.001765345.specialFormat())
print(0.00004839643.specialFormat())
print(1.2345.specialFormat())

Output:

0.001765
0.00004840
1.23

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • That seems overly complicated for a simple use case. – Samah Jan 23 '18 at 00:26
  • @Samah How? 1. It's not simple. The OP specifically stated they wanted to handle two different cases. 2. Making it an extension makes it reusable. – rmaddy Jan 23 '18 at 00:28
  • I missed that part of the question. – Samah Jan 23 '18 at 00:29
  • 1
    I would just make the formatter a static property extending `Formatter` so you don't create a new `NumberFormatter` every time you call this method. Something similar to this https://stackoverflow.com/a/48334604/2303865 – Leo Dabus Jan 23 '18 at 00:35
  • @LeoDabus Good idea, though if it were being added to an extension I would probably make it `private` too. – Samah Jan 23 '18 at 01:37
  • Thank you both for your input . @rmaddy provided an awesome conditional solution for the whole problem – vikzilla Jan 23 '18 at 02:00
  • And @LeoDabus made a good point at optimizing it. I have marked this as best answer, and will provide my full solution as well :) – vikzilla Jan 23 '18 at 02:00
0

I used a combination of the answers / comments received to optimally solve this problem.

First, the two necessary formatters as static properties via an extension (as suggested by Leo).

extension Formatter {
    static let twoDecimals: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.minimumFractionDigits = 2
        formatter.maximumFractionDigits = 2
        return formatter
    }()

    static let fourSigDigits: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.minimumSignificantDigits = 4
        formatter.maximumSignificantDigits = 4
        return formatter
    }()
}

Then, an extension to conditionally apply the proper formatter (as suggest by rmaddy):

extension FloatingPoint {
    var currencyFormat: String {
        return abs(self) >= 1 ? 
            Formatter.twoDecimals.string(for: self) ?? "" :
            Formatter.fourSigDigits.string(for: self) ?? ""
    }
}

Finally, you can use it like so:

let eth = 1200.123456
let xrp = 1.23456789
let trx = 0.07891234

eth.currencyFormat //"1200.12"
xrp.currencyFormat //"1.23"
trx.currencyFormat //"0.07891"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
vikzilla
  • 3,998
  • 6
  • 36
  • 57
  • 2
    You can simply extend FloatingPoint protocol to extend all floating point types and use `string(for:)` method. Btw considering that there is no parameters needed you can make it a read only computed property instead of a method – Leo Dabus Jan 23 '18 at 02:08
  • `extension FloatingPoint { var currencyFormat: String { return abs(self) >= 1 ? Formatter.twoDecimals.string(for: self) ?? "" : Formatter.fourSigDigits.string(for: self) ?? "" } }` – Leo Dabus Jan 23 '18 at 02:12
  • @LeoDabus Edited to reflect your improvements - awesome! – vikzilla Jan 23 '18 at 02:19
  • @LeoDabus I have noticed one flaw actually. If the number is greater than 1, and has a second decimal of 0. For example, 12.30. I will be displayed as 12.3. Any idea how this can be accounted for? Otherwise thank you for all your input – vikzilla Jan 23 '18 at 02:29
  • @LeoDabus Perfect. Thanks again for all your help - ended up with a great solution – vikzilla Jan 23 '18 at 02:36