1

I have a problem to sort some numbers, which are string first. The numbers are too huge for UInt64, so i converted the string numbers to float and then sorted it. That works out great. But then i need to print these numbers with no decimals. So I tried to format the numbers. But the Bigger number are changing its value after formatting it.

Here is the Input array to sort -
["6","31415926535897932384626433832795","1","3","10","3","5"]

And I need to print output in exactly this format -

1

3

3

5

10

31415926535897932384626433832795

Here is my code in swift -

import Foundation
var a = Array<Float>()
var b = ["6","31415926535897932384626433832795","1","3","10","3","5"]
a = b.map{ Float($0)! }
for i in 0..<(a.count-1){
    var min = i
    for j in (i+1)..<a.count{
        if a[j] < a[min] {
            min = j
        }
    }

    var temp = a[i]
    a[i] = a[min]
    a[min] = temp
}
for val in a{
    print(String(format: "%.0f",val.rounded(.down)))
}

My Output is -

1

3

3

5

6

10

31415927314585224784361549725696

If you notice the last biggest number is changed from the original input. Any suggestions would be much appreciated! Thanks!

Richa Srivastava
  • 482
  • 1
  • 7
  • 24
  • The sorting has nothing to do with your issue. If you do your `for val in a{ print(String(format: "%.0f",val.rounded(.down)))}` before your sort (just after the `b.map{ Float($0)! }`), you'll notice the same issue. – Larme Aug 07 '17 at 12:34
  • Using `Float`, the sorted output of `["6","31415926535897932384626433832797","31415926535897932384626433832796","31415926535897932384626433832795","1", "3"]` is `["1", "3", "6", "31415926535897932384626433832797", "31415926535897932384626433832796", "31415926535897932384626433832795"]`. Can you accept this result? – OOPer Aug 07 '17 at 12:55
  • https://stackoverflow.com/questions/30260769/converting-int-to-float-loses-precision-for-large-numbers-in-swift. This would help you. – Mohammad Sadiq Aug 07 '17 at 13:01

3 Answers3

5

You can use numeric comparison:

let values = ["6","31415926535897932384626433832795","1","3","10","3","5"]

let sortedValues = values.sorted { (value1, value2) in
    let order = value1.compare(value2, options: .numeric)
    return order == .orderedAscending
}

print(sortedValues) // ["1", "3", "3", "5", "6", "10", "31415926535897932384626433832795"]
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Playground execution failed: error: MyPlayground.playground:6:17: error: value of type 'String' has no member 'compare' let order = value1.compare(value2, options: .numeric) ^~~~~~ ~~~~~~~ – Richa Srivastava Aug 07 '17 at 13:18
  • @RichaSrivastava, you need to `import Foundation`. – OOPer Aug 07 '17 at 13:22
  • Tested in both Swift 3 and Swift 4 environments, you probably need `Foundation` import. – Sulthan Aug 07 '17 at 13:22
3

With using float, you may get unexpected result for orders between big ints:

var str = "Hello, playground"
let inArray = [
    "6",
    "31415926535897932384626433832797",
    "31415926535897932384626433832796",
    "31415926535897932384626433832795",
    "1",
    "3"
]
let outArray = inArray.sorted {Float($0)! < Float($1)!}
print(outArray)
//->["1", "3", "6", "31415926535897932384626433832797", "31415926535897932384626433832796", "31415926535897932384626433832795"]

As described in mag_zbc's answer, Float has about only 7 significant digits, so all less significant digits are lost.

Float("31415926535897932384626433832797") == Float("31415926535897932384626433832795")
//->true

If the digits of your numbers are 38 at most, you can use Decimal (as also suggested in mag_zbc's answer).

let outWithDecimal = inArray.sorted {Decimal(string: $0)! < Decimal(string: $1)!}
print(outWithDecimal)
//->["1", "3", "6", "31415926535897932384626433832795", "31415926535897932384626433832796", "31415926535897932384626433832797"]

Or else, if your data contains numbers with more than 38 digits, String comparison would work with a little pre-processing:

(Assuming all your numbers are non-negative integer.)

extension String {
    func fillZeroLeft(_ length: Int) -> String {
        if self.characters.count < length {
            return String.init(repeating: "0", count: length-self.characters.count) + self
        } else {
            return self
        }
    }
}

let maxLen = inArray.lazy.map{$0.characters.count}.max()!
let outWithString = inArray.sorted {$0.fillZeroLeft(maxLen) < $1.fillZeroLeft(maxLen)}
print(outWithString)
//->["1", "3", "6", "31415926535897932384626433832795", "31415926535897932384626433832796", "31415926535897932384626433832797"]

(But numeric comparison in Sulthan's answer seems to be better for me!)

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • By the way, instead of padding the string with zeros, you can simply first compare the length, e.g. `sorted { $0.length < $1.length || $0 < $1 }`. (assuming positive integers). – Sulthan Aug 07 '17 at 13:50
  • @Sulthan, that seems to be a nice idea. But it seems to be missing some cases. Check if you can get a right result for `($0, $1) = ("123", "99")`. – OOPer Aug 07 '17 at 13:59
  • My mistake, it would have to be `$0.length == $1.length ? $0 < $1 : $0.length < $1.length`. – Sulthan Aug 07 '17 at 14:17
  • 1
    @Sulthan, that should work. Or `{ $0.length < $1.length || $0.length == $1.length && $0 < $1 }` is equivalent. – OOPer Aug 07 '17 at 14:20
0

It's impossible to accurately represent such big numbers using primitive types in Swift.

Your value is ~3e+32
UInt64 has a max value of ~1,8e+18
Float uses 24 bits for a significant, which allows to accurately represent a number up to ~1.6e+8 Double uses 53 bits for significant, which allows to accurately represent a number up to ~9e+15

Floating point types (Float and Double) are able to hold much greater values, but they will lose accuracy above the numbers their significants can hold.

Apart from using some external library, I'd suggest using NSDecimalNumber class, which is supposed to be able to hold up to 38 decimal digits - which is enough for numbers in your example. However, if you need even bigger numbers, then you'll need to look for some external library.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
mag_zbc
  • 6,801
  • 14
  • 40
  • 62