Tengo un problema de precisión al tratar con la entrada de moneda usando el tipo Decimal. El problema es con el formateador. Este es el código mínimo reproducible en el patio de recreo:
let formatter = NumberFormatter() formatter.numberStyle = .currency formatter.isLenient = true formatter.maximumFractionDigits = 2 formatter.generatesDecimalNumbers = true let text = "89806.9" let decimal = formatter.number(from: text)?.decimalValue ?? .zero let string = "\(decimal)" print(string) Imprime 89806.89999999999 en lugar de 89806.9 . Sin embargo, la mayoría de los otros números están bien (por ejemplo 8980.9 ). Así que no creo que este sea un problema de doble vs decimal.
Editar:
La razón por la que necesito usar el formateador es que a veces necesito lidiar con la entrada de formato de moneda:
let text = "$89,806.9" let decimal = formatter.number(from: text)?.decimalValue ?? .zero print("\(decimal)") // prints 89806.89999999999 let text2 = "$89,806.9" let decimal2 = Decimal(string: text2) print("\(decimal2)") // prints nilEstoy de acuerdo en que este es un error sorprendente, y abriría un comentario de Apple al respecto, pero también recomendaría cambiar a Decimal(string:locale:) en lugar de un formateador, que logrará su objetivo (excepto quizás la parte isLenient ).
let x = Decimal(string: text)! print("\(x)") // 89806.9 Si desea corregir los dígitos de las fracciones, puede aplicar el redondeo con bastante facilidad con conversiones * 100 / 100 100/100 a través de Int. (Explicaré si no es obvio cómo hacer esto; funciona para Decimal, aunque no para Double).
Si esto es estrictamente un problema de representación y solo está buscando traducir un valor de moneda de una cadena sin procesar a una cadena formateada, simplemente hágalo.
let formatter = NumberFormatter() formatter.numberStyle = .currency let raw = "89806.9" if let double = Double(raw), let currency = formatter.string(from: NSNumber(value: double)) { print(currency) // $89,806.90 }Si hay matemáticas involucradas, entonces, antes de usar los formateadores de cadenas, le indicaría ¿ Por qué no usar Double o Float para representar la moneda? y Cómo redondear un doble a un int utilizando Banker's Rounding in C como excelentes puntos de partida.
Usar el nuevo FormatStyle parece generar el resultado correcto
let format = Decimal.FormatStyle .number .precision(.fractionLength(0...2)) let text = "89806.9" let value = try! format.parseStrategy.parse(text)A continuación se muestra un ejemplo de análisis de una moneda utilizando el código de moneda de la configuración regional
let currencyFormat = Decimal.FormatStyle.Currency .currency(code: Locale.current.currencyCode!) .precision(.fractionLength(0...2)) let amount = try! currencyFormat.parseStrategy.parse(text)Ejemplo sueco:
let text = "89806,9 kr" print(amount)89806.9
Otra opción es usar el nuevo init para Decimal que toma una Cadena y un FormatStyle.Currency (o un Número o Porcentaje)
let amount = try Decimal(text, format: currencyFormat) y para formatear este valor podemos usar formatted(_:) en Decimal
print(amount.formatted(currencyFormat))Salida (todavía sueco):
89 806,9 €
Recibo mi respuesta con valor doble y elimino la línea formatter.generatesDecimalNumbers para que funcione.
let formatter = NumberFormatter() formatter.numberStyle = .currency formatter.isLenient = true formatter.maximumFractionDigits = 2 //formatter.generatesDecimalNumbers = true // I removed this line let text = "$89806.9" let double = formatter.number(from: text)?.doubleValue ?? .zero // converting as double or float let string = "\(double)" print(string) // 89806.9 let anotherText = "$0.1" let anotherDouble = formatter.number(from: anotherText)?.doubleValue ?? .zero // converting as double or float let anotherString = "\(anotherDouble)" print(anotherString) // 0.1