Swift - Як конвертувати String в Double


242

Я намагаюся швидко написати програму BMI. І у мене з’явилася ця проблема: як перетворити String в Double?

У Objective-C я можу так:

double myDouble = [myString doubleValue];

Але як я можу досягти цього швидкою мовою?


1
Це змінилося в Swift 2 см новий відповідь , використовуючи новий Double () failable ініціалізатор: stackoverflow.com/a/32850058/276626
Paul Solt

Відповіді:


205

Swift 4.2+ Рядок удвічі

Ви повинні використовувати ініціалізатори нового типу для перетворення між рядковими та числовими типами (Double, Float, Int). Він поверне необов'язковий тип (Double?), Який матиме правильне значення або нуль, якщо String не був числом.

Примітка: Властивість doubleValue NSString не рекомендується, оскільки вона повертає 0, якщо значення не вдалося перетворити (тобто: неправильний ввід користувача).

let lessPrecisePI = Float("3.14")

let morePrecisePI = Double("3.1415926536")
let invalidNumber = Float("alphabet") // nil, not a valid number

Розгортайте значення, щоб використовувати їх, використовуючи if / let

if let cost = Double(textField.text!) {
    print("The user entered a value price of \(cost)")
} else {
    print("Not a valid number: \(textField.text!)")
}

Ви можете конвертувати відформатовані номери та валюту за допомогою NumberFormatterкласу.

let formatter = NumberFormatter()
formatter.locale = Locale.current // USA: Locale(identifier: "en_US")
formatter.numberStyle = .decimal
let number = formatter.number(from: "9,999.99")

Валютні формати

let usLocale = Locale(identifier: "en_US")
let frenchLocale = Locale(identifier: "fr_FR")
let germanLocale = Locale(identifier: "de_DE")
let englishUKLocale = Locale(identifier: "en_GB") // United Kingdom
formatter.numberStyle = .currency

formatter.locale = usLocale
let usCurrency = formatter.number(from: "$9,999.99")

formatter.locale = frenchLocale
let frenchCurrency = formatter.number(from: "9999,99€")
// Note: "9 999,99€" fails with grouping separator
// Note: "9999,99 €" fails with a space before the €

formatter.locale = germanLocale
let germanCurrency = formatter.number(from: "9999,99€")
// Note: "9.999,99€" fails with grouping separator

formatter.locale = englishUKLocale
let englishUKCurrency = formatter.number(from: "£9,999.99")

Детальніше читайте в моєму блозі про перетворення рядків у подвійні типи (та валюти) .


2
Інша проблема полягає в тому, що він не підтримує номери в іншій мові, окрім 1000,00, якщо ви використовуєте інші формати на зразок 1 000,00, він би відрізав 2 десяткові знаки в кінці. Принаймні, це те, що відбувається для Objective-C. - Відповідно до документації Apple: (Вони не знають локально) Наступними зручними методами пропускаються всі символи початкового простору (whitespaceSet) та ігноруються символи, що відкладаються. Вони не відомі за місцевою ознакою. NSScanner або NSNumberFormatter можна використовувати для більш потужного та відомого для локального розбору чисел.
mskw

@mskw Я додав додаткові деталі щодо використання NumberFormatter для розбору валюти. У своєму коді я з'єднаю декілька спроб перетворення форматів за допомогою синтаксису if / let, щоб я міг розібрати як форматизовані числа валюти ($ 1,000.00), так і форматовані числа (1000). Ви не завжди знаєте, як користувач буде вводити цифри, тому можливість підтримувати обидва ідеально підходить для групи операторів if / let.
Пол Солт

276

Swift 2 Update Є нові failable ініціалізаторів , які дозволяють зробити це більш ідіоматичний і безпечним способом (як і багато відповідей відзначили, подвійне значення NSString є не дуже безпечно , тому що вона повертає значення 0 для значень , які не є цифр. Це означає , що doubleValueз "foo"і "0"є так само.)

let myDouble = Double(myString)

Це повертається необов’язково, тож у випадках, коли передача, "foo"де doubleValueповернулося б 0, недоступний ініціалізатор повернеться nil. Ви можете використовувати guard, if-letабо mapдля оброблятиOptional<Double>

Оригінальна публікація: Вам не потрібно використовувати конструктор NSString, як пропонує прийнята відповідь. Ви можете просто провести його таким чином:

(swiftString as NSString).doubleValue

5
охай, Ярсен. Недоліком цього підходу є те, що він не вийде з ладу, якщо рядок не є дійсним подвійним. Замість цього ви просто отримаєте 0,0. Тобто, результат - [NSString doubleValue]знаходиться Doubleв стриже як з'єднаний з Double?( Optional<Double>). Це відрізняється від функції Swift String, toInt()яка повертається Int?і в документах заявляє, що вона "... приймає рядки, які відповідають регулярному виразу" [- +]? [0-9] + "."
Деррік Хетвейвей

Так, використання .toInt()було б краще. Однак це була більше відповідь на те, як зробити саме те саме, але в Swift.
Ярсен

Цей підхід щільно з’єднує вас із Foundation, і, ймовірно, не працюватиме на будь-яких реалізаціях Swift, що не є Apple.
Ерік Кербер

5
У Swift 2.0 Double.init?(_ text: String)тепер доступний ініціалізатор, що доступний.
міксель

3
Це не найкращий підхід. Він вийде з ладу залежно від десяткового роздільника. Це буде добре з "." але не з ','. NSNumberFormatter буде підтримувати десятковий роздільник, який використовується в поточній локалізації.
Julian Król

75

Для трохи більше відчуття Swift, використовуючи NSFormatter()ухиляється від трансляції NSStringта повертається, nilколи рядок не містить Doubleзначення (наприклад, "test" не повернеться 0.0).

let double = NSNumberFormatter().numberFromString(myString)?.doubleValue

Крім того, розширення Stringтипу Swift :

extension String {
    func toDouble() -> Double? {
        return NumberFormatter().number(from: self)?.doubleValue
    }
}

і використовувати його так toInt():

var myString = "4.2"
var myDouble = myString.toDouble()

Це повертає необов’язкове, Double?яке потрібно розкрутити.

Або з примусовим розгортанням:

println("The value is \(myDouble!)") // prints: The value is 4.2

або із заявою if let:

if let myDouble = myDouble {
    println("The value is \(myDouble)") // prints: The value is 4.2
}

Оновлення: Для локалізації дуже просто застосувати локалі до NSFormatter наступним чином:

let formatter = NSNumberFormatter()
formatter.locale = NSLocale(localeIdentifier: "fr_FR")
let double = formatter.numberFromString("100,25")

Нарешті, ви можете використовувати NSNumberFormatterCurrencyStyleна форматері, якщо ви працюєте з валютами, де рядок містить символ валюти.


Мені подобається ідея розширення класу String для додатків, які будуть використовувати конверсію в декількох місцях. Але я не бачу користі у складності виклику NSNumberFormatter, оскільки передача до NSString та використання .doubleValue здається принаймні такою самою прямою та легшою для читання та запам'ятовування.
clearlight

12
Насправді перевагою використання NSNumberFormatter () є те, що метод numberFromString: поверне нуль, якщо рядок містить символи, які не складають подвійних. Проблема DoubleValue на NSString полягає в тому, що він завжди повертає подвійний (0,0 для "тестування", наприклад) незалежно. Отже, якщо ви маєте справу з усіма видами рядків і хочете перевірити, чи є рядок просто подвійним, без додаткових символів, використовуючи NSNumberFormatter - це шлях.
dirkgroten

4
Це не вдається на французькій мові.
Петро К.

1
Це чудове рішення, але, використовуючи лише цей код, він не працюватиме в країнах, де коси використовуються як роздільники (включаючи Францію як згадуваний @PeterK.). Я спробував написати коментар із рішенням, але це було занадто довго, тому мені довелося замість цього зробити відповідь. Ось вам: Змінене рішення
Jacob R

53

Інший варіант тут - перетворити це на NSStringі використовувати це:

let string = NSString(string: mySwiftString)
string.doubleValue

13
Мені б хотілося, щоб це було приємніше. Як я можу дізнатися, якщо розбір не вдався, наприклад.
Райан Хоффман

2
@RyanHoffman На жаль, контракт з подвійною валю NSString ніколи нічого з цього не передбачав. Все, що ви отримуєте, - 0,0, якщо рядок не починається з дійсного представлення числа, HUGE_VAL та -HUGE_VAL для переповнення / переливання. Якщо ви хочете знати, чому не вдалося проаналізувати, я вважаю, що вам потрібно вивчити NSNumberFormatter, який був розроблений для більш чіткого перетворення числа / рядка.
Ярсен

Оскільки відповідь може бути складно знайти, ось вона:(swiftString as NSString).doubleValue
LearnCocos2D

24

Ось метод розширення, який дозволяє просто викликати doubleValue () в рядку Swift і отримувати подвійний зворотній зв'язок (приклад виводу приходить першим)

println("543.29".doubleValue())
println("543".doubleValue())
println(".29".doubleValue())
println("0.29".doubleValue())

println("-543.29".doubleValue())
println("-543".doubleValue())
println("-.29".doubleValue())
println("-0.29".doubleValue())

//prints
543.29
543.0
0.29
0.29
-543.29
-543.0
-0.29
-0.29

Ось метод розширення:

extension String {
    func doubleValue() -> Double
    {
        let minusAscii: UInt8 = 45
        let dotAscii: UInt8 = 46
        let zeroAscii: UInt8 = 48

        var res = 0.0
        let ascii = self.utf8

        var whole = [Double]()
        var current = ascii.startIndex

        let negative = current != ascii.endIndex && ascii[current] == minusAscii
        if (negative)
        {
            current = current.successor()
        }

        while current != ascii.endIndex && ascii[current] != dotAscii
        {
            whole.append(Double(ascii[current] - zeroAscii))
            current = current.successor()
        }

        //whole number
        var factor: Double = 1
        for var i = countElements(whole) - 1; i >= 0; i--
        {
            res += Double(whole[i]) * factor
            factor *= 10
        }

        //mantissa
        if current != ascii.endIndex
        {
            factor = 0.1
            current = current.successor()
            while current != ascii.endIndex
            {
                res += Double(ascii[current] - zeroAscii) * factor
                factor *= 0.1
                current = current.successor()
           }
        }

        if (negative)
        {
            res *= -1;
        }

        return res
    }
}

Немає перевірки помилок, але ви можете додати її, якщо вона вам потрібна.


20

З Swift 1.1 ви можете безпосередньо перейти Stringдо const char *параметра.

import Foundation

let str = "123.4567"
let num = atof(str) // -> 123.4567

atof("123.4567fubar") // -> 123.4567

Якщо вам не подобається застаріле atof:

strtod("765.4321", nil) // -> 765.4321

Одне застереження: поведінка конверсії відрізняється від NSString.doubleValue.

atofі strtodприйняти 0xпопередньо встановлений шістнадцятковий рядок:

atof("0xffp-2") // -> 63.75
atof("12.3456e+2") // -> 1,234.56
atof("nan") // -> (not a number)
atof("inf") // -> (+infinity)

Якщо ви віддаєте перевагу .doubleValueповедінці, ми все одно можемо використовувати CFStringмости:

let str = "0xff"
atof(str)                      // -> 255.0
strtod(str, nil)               // -> 255.0
CFStringGetDoubleValue(str)    // -> 0.0
(str as NSString).doubleValue  // -> 0.0

Я прихильник такого підходу, а не кастинг NSString.
Сем Соффс

10

У Swift 2.0 найкращий спосіб уникнути думки, як розробник Objective-C. Таким чином, ви не повинні "перетворювати рядок у подвійну", але ви повинні "ініціалізувати подвійний з рядка". Документ Apple тут: https://developer.apple.com/library/ios//documentation/Swift/Reference/Swift_Double_Structure/index.html#//apple_ref/swift/structctr/Double/s:FSdcFMSdFSSGSqSd_

Це необов'язковий init, тому ви можете використовувати оператор злиття нуля (??), щоб встановити значення за замовчуванням. Приклад:

let myDouble = Double("1.1") ?? 0.0

Double.init?(_ text: String)стають доступними у Swift 2.0. Ви повинні згадати про це. Інші відповіді тут не тому, що люди думають, як розробники Objective-C, а тому, що цей ініціалізатор раніше не був доступний.
міксель

Дійсно, ти маєш рацію. Це доступно лише у Swift 2.0, я це згадаю :) Але, як і в інших мовах, ви ініціалізуєте тип з іншим, і не слід очікувати, що тип зможе перетворитись на інший. Довгі дискусії, але як розробник Obj-C у мене є багато поганих практик кодування з Swift, і це одна з них :)
Toom,

10

У SWIFT 3 ви можете використовувати:

if let myDouble = NumberFormatter().number(from: yourString)?.doubleValue {
   print("My double: \(myDouble)")
}

Примітка: - Якщо рядок містить будь-які символи, окрім числових цифр або відповідної місцевості групи або десяткових роздільників, аналіз не вдасться. - Будь-які провідні або кінцеві символи розділювача простору в рядку ігноруються. Наприклад, рядки "5", "5" і "5" створюють число 5.

Взяте з документації: https://developer.apple.com/reference/foundation/numberformatter/1408845- номер


10

Я не бачив відповіді, яку шукав. Я просто розміщу тут мій, якщо це може допомогти будь-кому. Ця відповідь справедлива, лише якщо вам не потрібен певний формат .

Швидкий 3

extension String {
    var toDouble: Double {
        return Double(self) ?? 0.0
    }
}

9

Спробуйте це:

   var myDouble = myString.bridgeToObjectiveC().doubleValue
   println(myDouble)

ПРИМІТКА

Видалено в бета-версії 5. Це більше не працює?


1
bridgeToObjectiveC () видалено в Xcode 6 Beta 5. Метод містера Смайлі - найшвидший, який я знайшов на даний момент
faraquet99

7

Це спирається на відповідь @Ryu

Його рішення чудове, якщо ви перебуваєте в країні, де крапки використовуються як роздільники. За замовчуванням NSNumberFormatterвикористовується локальний пристрій. Тому це не вдасться у всіх країнах, де в якості роздільника за замовчуванням використовується кома (включаючи Францію, як згадується @PeterK.), Якщо число використовує крапки як роздільники (що зазвичай є). Щоб встановити локаль цього NSNumberFormatter як US, і, таким чином, використовувати точки, як роздільники замінять рядок

return NSNumberFormatter().numberFromString(self)?.doubleValue

з

let numberFormatter = NSNumberFormatter()
numberFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
return numberFormatter.numberFromString(self)?.doubleValue

Тому повний код стає

extension String {
    func toDouble() -> Double? {
        let numberFormatter = NSNumberFormatter()
        numberFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
        return numberFormatter.numberFromString(self)?.doubleValue
    }
}

Щоб скористатися цим, просто зателефонуйте "Your text goes here".toDouble()

Це поверне необов’язково Double?

Як згадував @Ryu, ви можете змусити його розгортатись:

println("The value is \(myDouble!)") // prints: The value is 4.2

або використовувати if letоператор:

if let myDouble = myDouble {
    println("The value is \(myDouble)") // prints: The value is 4.2
}

1
Це правильна відповідь. Версія Swift 3 буде NumberFormatter().number(from: myString)?.doubleValue
nicolas leo

7

SWIFT 4

extension String {
    func toDouble() -> Double? {
        let numberFormatter = NumberFormatter()
        numberFormatter.locale = Locale(identifier: "en_US_POSIX")
        return numberFormatter.number(from: self)?.doubleValue
    }
}


6

Швидкий: 4 і 5

Можливо, це два способи:

  1. Рядок -> Int -> Double:

    let strNumber = "314"
    if let intFromString = Int(strNumber){
        let dobleFromInt = Double(intFromString)
        print(dobleFromInt)
    }
  2. Рядок -> NSString -> Подвійний

    let strNumber1 = "314"
    let NSstringFromString = NSString(string: strNumber1)
    let doubleFromNSString = NSstringFromString.doubleValue
    print(doubleFromNSString)

Використовуйте його як завгодно відповідно до необхідності коду.


Перший метод для мене не працює, проте другий працює досить добре.
Саураб

@Saurabh, Чи можете ви, будь ласка, детальніше розповісти про те, якою була ваша струна?
Abhirajsinh Thakore

5

Будь ласка, перевірте це на дитячому майданчику!

let sString = "236.86"

var dNumber = NSNumberFormatter().numberFromString(sString)
var nDouble = dNumber!
var eNumber = Double(nDouble) * 3.7

До речі, у моєму Xcode

.toDouble () - не існує

.doubleValue створити значення 0,0 з не числових рядків ...


4

1.

let strswift = "12"
let double = (strswift as NSString).doubleValue

2.

var strswift= "10.6"
var double : Double = NSString(string: strswift).doubleValue 

Можливо, це допоможе вам.


4

Як уже зазначалося, найкращий спосіб досягти цього - прямий кастинг:

(myString as NSString).doubleValue

Виходячи з цього, ви можете зробити гладке розширене Swift String:

extension String {
    var doubleValue: Double {
        return (self as NSString).doubleValue
    }
}

Це дозволяє безпосередньо використовувати:

myString.doubleValue

Який виконає кастинг для вас. Якщо Apple все-таки додасть doubleValueдо рідного рядка, вам просто потрібно видалити розширення, а решта вашого коду автоматично складеться штрафом!


Метод Apple буде називатися .toDouble ... таким же, як iInt. Це дивний упущення. Ймовірно, буде виправлено у майбутній версії Swift. Мені найкраще подобається метод розширення, він підтримує чистий код.
n13,

Повернене значення повинно бути подвійним НЕ
поплавцем

4

SWIFT 3

Щоб зрозуміти, сьогодні існує метод за замовчуванням:

public init?(_ text: String)з Doubleкласу.

Його можна використовувати для всіх класів.

let c = Double("-1.0") let f = Double("0x1c.6") let i = Double("inf") тощо.


2

Розширення з додатковою локальною формою

Швидкий 2.2

extension String {
    func toDouble(locale: NSLocale? = nil) -> Double? {
        let formatter = NSNumberFormatter()
        if let locale = locale {
            formatter.locale = locale
        }
        return formatter.numberFromString(self)?.doubleValue
    }
}

Швидкий 3.1

extension String {
    func toDouble(_ locale: Locale) -> Double {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.locale = locale
        formatter.usesGroupingSeparator = true
        if let result = formatter.number(from: self)?.doubleValue {
            return result
        } else {
            return 0
        }
    }
}

1
Я додав приклад Swift 3.1. Що ви маєте на увазі під "... і додайте приклад для мови"? 🙂
imike

1

Або ви могли зробити:

var myDouble = Double((mySwiftString.text as NSString).doubleValue)

1

Ви можете використовувати StringEx . Він поширюється Stringна перетворення рядків у число, включаючи toDouble().

extension String {
    func toDouble() -> Double?
}

Він перевіряє рядок і відмовляється, якщо його неможливо перетворити в подвійний.

Приклад:

import StringEx

let str = "123.45678"
if let num = str.toDouble() {
    println("Number: \(num)")
} else {
    println("Invalid string")
}


0

Що також працює:

// Init default Double variable
var scanned: Double()

let scanner = NSScanner(string: "String to Scan")
scanner.scanDouble(&scanned)

// scanned has now the scanned value if something was found.

ПРИМІТКА: "scanDouble" було застаріло в iOS 13.0
uplearnedu.com

0

Використовуйте цей код у Swift 2.0

let strWithFloat = "78.65" let floatFromString = Double(strWithFloat)


0

Використання Scannerв деяких випадках є дуже зручним способом вилучення чисел із рядка. І це майже настільки ж потужно, як і NumberFormatterпри розшифровці та роботі з різними форматами та локальними номерами. Він може витягувати числа та валюти за допомогою різних десяткових та групових роздільників.

import Foundation
// The code below includes manual fix for whitespaces (for French case)
let strings = ["en_US": "My salary is $9,999.99",
               "fr_FR": "Mon salaire est 9 999,99€",
               "de_DE": "Mein Gehalt ist 9999,99€",
               "en_GB": "My salary is £9,999.99" ]
// Just for referce
let allPossibleDecimalSeparators = Set(Locale.availableIdentifiers.compactMap({ Locale(identifier: $0).decimalSeparator}))
print(allPossibleDecimalSeparators)
for str in strings {
    let locale = Locale(identifier: str.key)
    let valStr = str.value.filter{!($0.isWhitespace || $0 == Character(locale.groupingSeparator ?? ""))}
    print("Value String", valStr)

    let sc = Scanner(string: valStr)
    // we could do this more reliably with `filter` as well
    sc.charactersToBeSkipped = CharacterSet.decimalDigits.inverted
    sc.locale = locale

    print("Locale \(locale.identifier) grouping separator: |\(locale.groupingSeparator ?? "")| . Decimal separator: \(locale.decimalSeparator ?? "")")
    while !(sc.isAtEnd) {
        if let val = sc.scanDouble() {
            print(val)
        }

    }
}

Однак існують проблеми з розділювачами, які можна розглядати як роздільники слів.

// This doesn't work. `Scanner` just ignores grouping separators because scanner tends to seek for multiple values
// It just refuses to ignore spaces or commas for example.
let strings = ["en_US": "$9,999.99", "fr_FR": "9999,99€", "de_DE": "9999,99€", "en_GB": "£9,999.99" ]
for str in strings {
    let locale = Locale(identifier: str.key)
    let sc = Scanner(string: str.value)
    sc.charactersToBeSkipped = CharacterSet.decimalDigits.inverted.union(CharacterSet(charactersIn: locale.groupingSeparator ?? ""))
    sc.locale = locale
    print("Locale \(locale.identifier) grouping separator: \(locale.groupingSeparator ?? "") . Decimal separator: \(locale.decimalSeparator ?? "")")
    while !(sc.isAtEnd) {
        if let val = sc.scanDouble() {
            print(val)
        }

    }
}
//     sc.scanDouble(representation: Scanner.NumberRepresentation) could help if there were .currency case

Немає проблем з автоматичним виявленням локальної точки. Зауважте, що groupingSeparator у французькій мові у рядку "Mon salaire est 9 999,99 €" не є пробілом, хоча він може відображатися саме як простір (тут його немає). Ось чому код нижче працює добре, не !$0.isWhitespaceсимволи не відфільтровуються.

let stringsArr = ["My salary is $9,999.99",
                  "Mon salaire est 9 999,99€",
                  "Mein Gehalt ist 9.999,99€",
                  "My salary is £9,999.99" ]

let tagger = NSLinguisticTagger(tagSchemes: [.language], options: Int(NSLinguisticTagger.Options.init().rawValue))
for str in stringsArr {
    tagger.string = str
    let locale = Locale(identifier: tagger.dominantLanguage ?? "en")
    let valStr = str.filter{!($0 == Character(locale.groupingSeparator ?? ""))}
    print("Value String", valStr)

    let sc = Scanner(string: valStr)
    // we could do this more reliably with `filter` as well
    sc.charactersToBeSkipped = CharacterSet.decimalDigits.inverted
    sc.locale = locale

    print("Locale \(locale.identifier) grouping separator: |\(locale.groupingSeparator ?? "")| . Decimal separator: \(locale.decimalSeparator ?? "")")
    while !(sc.isAtEnd) {
        if let val = sc.scanDouble() {
            print(val)
        }

    }
}
// Also will fail if groupingSeparator == decimalSeparator (but don't think it's possible)

-1

Я вважаю більш читаним, щоб додати розширення до String наступним чином:

extension String {
    var doubleValue: Double {
        return (self as NSString).doubleValue
    }
}

і тоді ви просто можете написати свій код:

myDouble = myString.doubleValue

-1

моя проблема була комою, тому я вирішую її так:

extension String {
    var doubleValue: Double {
        return Double((self.replacingOccurrences(of: ",", with: ".") as NSString).doubleValue)
    }
}


-6

ми можемо використовувати значення CDouble, яке буде отримано myString.doubleValue


Помилка: у "String" немає учасника з ім'ям "doubleValue"
Matthew Knippen

@Matthew Так, немає жодного учасника, який називається "подвійне значення", тому ми можемо використовувати значення
CDouble

1
en.wikipedia.org/wiki/… Перегляньте складні номери та прочитайте довідкову документацію Swift. Немає жодного CDouble ... Ви говорите про Swift чи іншу мову? І я маю на увазі швидкий без розширень?
Адріан Слуйтерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.