Search code examples
iosswifttype-conversionmetalbigint

Why is Swift 5 String(Int) Failing when a Big Integer of over 20 digits?


I wrote the above-referenced simple code to check if integers in the Fibonacci sequence do not contain 0 or 5, and reduce to 1237, if the integer only contains 1,2,3,4,6,7,8, or 9 as digits; and if so, to then print the member of the sequence. Interestingly from a numbers game perspective, there are only 23 such integers in the Fibonacci sequence.

I have to use the Swift-BigInt library for when the integers get large:

func getFib1237s() {
  // Some temporary variables.
  var a = BInt(0)
  var b = BInt(1)
  var m = BInt(1)
  var i = BInt(0)
  var z = BInt(1)
  // Get the numbers until crash...
  while i < z {
    let temp = a
    a = b
    b = b + temp
    print("a: ", a)
    var str = String(a)
    print("String start: ", str)
    str = str.replacingOccurrences(of: "9", with: "3")
    print("String after 9 reducto: ", str)
    str = str.replacingOccurrences(of: "6", with: "23")
    print("String after 6 reducto: ", str)
    str = str.replacingOccurrences(of: "8", with: "2")
    print("String after 8 reducto: ", str)
    str = str.replacingOccurrences(of: "4", with: "2")
    print("String after 4 reducto: ", str)
    if (str.firstIndex(of:"5") == nil) && (str.firstIndex(of: "0") == nil) && str.contains("1") && str.contains("2") && str.contains("3") && str.contains("7")  {
            print(m, "Fib 1237 number is ", a, " | Digits: ", str.count)
            m+=1
        }
    i+=1
    z+=1
  }
}

Apparently, at or around the 20-digit mark, the String() method fails and throws errors, not performing the check, because, according to the debugger, the integer is being changed to random other integer entirely.

So, are there any BigInt or String workaround/alternatives in Swift? I wrote Ruby code that works fine in Xcode, but am trying to exclusively use Swift (and metal) for this project which ultimately needs to work on iOS for commercial/production purposes.


Solution

  • String(a) calls the String.init overload that takes BinaryInteger. It is very possible that this initialiser is not designed to handle numbers that are super large. You can use a.asString(radix: 10) to convert to string instead.

    To make your code work correctly, you should also:

    • remove the (str.firstIndex(of: "0") == nil)
    • declare a new string variable and assign the replaced strings to it, otherwise str.count would be incorrect.

    I would recommend writing a separate method called reduce, because "reducing" a string requires quite a few steps.

    Here is reduce:

    func reduce(_ s: String) -> String {
        let unique = String(Set(s))
        let replaced = unique.replacingOccurrences(of: "9", with: "3")
                                    .replacingOccurrences(of: "6", with: "23")
                                    .replacingOccurrences(of: "8", with: "2")
                                    .replacingOccurrences(of: "4", with: "2")
                                    .replacingOccurrences(of: "0", with: "")
        let sortedUniqueAgain = String(Set(replaced).sorted())
        return sortedUniqueAgain
    }
    

    Now, we can just check whether the return value of this method is 1237:

    while m <= 23 {
        let temp = a
        a = b
        b = b + temp
        let str = a.asString(radix: 10)
        // note that I have declared a new let constant here, instead of assigning to str
        // because otherwise str.count will be wrong
        let reduced = reduce(str)
        if reduced == "1237"  {
            print(m, "Fib 1237 number is ", a, " | Digits: ", str.count)
            m+=1
        }
    
    }
    

    Output:

    1 Fib 1237 number is  317811  | Digits:  6
    2 Fib 1237 number is  2178309  | Digits:  7
    3 Fib 1237 number is  267914296  | Digits:  9
    4 Fib 1237 number is  701408733  | Digits:  9
    5 Fib 1237 number is  1134903170  | Digits:  10
    6 Fib 1237 number is  72723460248141  | Digits:  14
    7 Fib 1237 number is  117669030460994  | Digits:  15
    8 Fib 1237 number is  8944394323791464  | Digits:  16
    9 Fib 1237 number is  14472334024676221  | Digits:  17
    10 Fib 1237 number is  37889062373143906  | Digits:  17
    11 Fib 1237 number is  420196140727489673  | Digits:  18
    12 Fib 1237 number is  1100087778366101931  | Digits:  19
    13 Fib 1237 number is  1779979416004714189  | Digits:  19
    14 Fib 1237 number is  2880067194370816120  | Digits:  19
    15 Fib 1237 number is  19740274219868223167  | Digits:  20
    16 Fib 1237 number is  83621143489848422977  | Digits:  20
    17 Fib 1237 number is  927372692193078999176  | Digits:  21
    18 Fib 1237 number is  781774079430987230203437  | Digits:  24
    19 Fib 1237 number is  1264937032042997393488322  | Digits:  25
    20 Fib 1237 number is  19134702400093278081449423917  | Digits:  29
    21 Fib 1237 number is  1983924214061919432247806074196061  | Digits:  34
    22 Fib 1237 number is  8404037832974134882743767626780173  | Digits:  34
    23 Fib 1237 number is  162926777992448823780908130212788963731840407743629812913410  | Digits:  60