2

I have the following Float: 1123455432.67899

My desired result is a String: 1,123,455,432.67899

Best case correct , and . based on location (US/Europe)

struct Number {
    static let withSeparator: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.groupingSeparator = ","
        formatter.numberStyle = .decimal
        return formatter
    }()
}

let myFloat: Float = 1123455432.67899
let myNumber = NSNumber(value: myFloat)
let formatter = Number.withSeparator
if let result = formatter.string(from: myNumber) {
   print(result)
}

This formatter works great as long as my Float has no decimals, als soon as I'm having decimals it starts to "calculate". It calculates up/down based on the 3rd decimal number.

What am I missing? What's the best way to get a String: 1,123,455,432.67899 from a Float with no matter how many decimal numbers? Help is very appreciated.

Edit:

My exact function:

func formatValue(_ value: String ) -> String {

    if let double = Double(value) {

        let formatter = Number.withSeparator
        if let result = formatter.string(from: NSNumber(value: double)) {
            return result
        } else {
            return value
        }
    } else {
        return value
    }
}

value is always a number for example 5.5555555555. But in this specific case the result = 5.556.

David Seek
  • 16,783
  • 19
  • 105
  • 136
  • Are you sure you want to specify the `grouping` separator as `,`? Not all places use it. – Alexander Apr 08 '17 at 16:38
  • no i don't need to have the separation forced by `,`, it was just example code – David Seek Apr 08 '17 at 16:39
  • 2
    A `Float` is a 32-bit binary floating point number. It can represent approx 7 decimal digits, compare https://en.wikipedia.org/wiki/IEEE_floating_point. For precise *decimal* arithmetic, use `(NS)DecimalNumber` – Martin R Apr 08 '17 at 16:46
  • 1
    For a general understanding, take the time to read http://stackoverflow.com/questions/588004/is-floating-point-math-broken and http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html. – Martin R Apr 08 '17 at 16:50
  • thank you for the links @MartinR ! – David Seek Apr 08 '17 at 16:53

3 Answers3

4

Use Double instead of Float, the value you are specifying is not well representable in Float:

// [... your current code ...]
let myDouble: Double = 1123455432.67899
let myNumber = NSNumber(value: myDouble)
// [... your current code ...]

1,123,455,432.679

The e+XX notation Float has by default is not just for show, it is there because Float cannot store all digits. See:

let myFloat2: Float = 1123455432.67899
print(myFloat2 == 1123455432) // true
let notRepresentable = Float(exactly:1123455432.67899) // nil
luk2302
  • 55,258
  • 23
  • 97
  • 137
1

Fundamentally, your issue comes from the floating point imprecision of Float. Using a double precision floating point data type (Double) will alleviate this, to an extent.

Also, you shouldn't hardcode the groupingSeperator, but instead let it be inferred from the current locale (which is the default behaviour).

import Foundation

let numberFormatter: NumberFormatter = {
    let nf = NumberFormatter()
    nf.numberStyle = .decimal
    return nf
}()   


let myDouble = 1123455432.67899
let myNumber = NSNumber(value: myDouble)

// It's also directly initializable from a literal
// let myNumber: NSNumber = 1123455432.67899 

guard let result = numberFormatter.string(from: myNumber) else { fatalError() }  

print(result)
Alexander
  • 59,041
  • 12
  • 98
  • 151
1

In addition to luk2302's answer, I would suggest to add it as a method to a Double extension, as follows:

extension Double {
    func formattedString(_ maximumFractionDigits: Int = 5) -> String? {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = maximumFractionDigits

        return numberFormatter.string(for:self)
    }
}

if let formattedString = 1123455432.67899.formattedString() {
    print(formattedString) // 1,123,455,432.67899
}

It might improve the ease of getting the desired string.

Thanks for @rmaddy and @LeoDabus for providing useful notes.

Community
  • 1
  • 1
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • What's the point of the `guard` statement? Simply return the result of `numberFormatter.string`. – rmaddy Apr 08 '17 at 17:08
  • Do not set the groupingSeparator. Let it default properly based on the user's locale. – rmaddy Apr 08 '17 at 17:08
  • Btw no need to initialize a new NSNumber object. just use for: instead of from: – Leo Dabus Apr 08 '17 at 17:08
  • @LeoDabus could you please elaborate? – Ahmad F Apr 08 '17 at 17:12
  • `string(for: double)` – Leo Dabus Apr 08 '17 at 17:13
  • 1
    @LeoDabus pretty cool! I didn't know that I am able to do such a thing, in my mind, there was a -wrong- guideline says: when working with `NumberFormatter`, there is must be a used `NSNumber` for reading it. Thanks alot :) – Ahmad F Apr 08 '17 at 17:19
  • Now you can extend FloatingPoint instead of Double – Leo Dabus Apr 08 '17 at 17:46
  • I would also move your formatter out of there. Make a static property extending Formatter protocol so you can use it without initializing a new Formatter every time you use it – Leo Dabus Apr 08 '17 at 17:49
  • @LeoDabus I've already tried to make the extension to be of `FloatingPoint`, but because the `NSNumber` is unable to take one a a parameter, I made to be an extension of `Double`, after getting rid of the `NSNumber`, it would be legal. Please feel free to edit my answer :) – Ahmad F Apr 08 '17 at 17:53
  • Using for instead of from you can just change it as it is – Leo Dabus Apr 08 '17 at 17:55
  • @LeoDabus Referring to your comment "I would also move your formatter out of there. Make a static property extending Formatter protocol so you can use it without initializing a new Formatter every time you use it", -when you are available-, could you please show us how it should be done? as a noob developer I'll be very happy if I learnt something new today... :) – Ahmad F Apr 08 '17 at 17:58