2008-11-25 15 views
15

Tengo algunas cadenas que son específicas de la configuración regional (p. Ej., 0.01 o 0.01). Quiero convertir esta cadena a un NSDecimalNumber. Desde el examples I've seen thus far on the interwebs, esto se logra mediante el uso de un NSNumberFormatter a la:Obteniendo un NSDecimalNumber desde una cadena local específica?

NSString *s = @"0.07"; 

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 
[formatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; 
[formatter setGeneratesDecimalNumbers:YES]; 
NSDecimalNumber *decimalNumber = [formatter numberFromString:s]; 

NSLog([decimalNumber stringValue]); // prints 0.07000000000000001 

estoy usando el modo de 10,4 (además de ser recomendado por la documentación, también es el único modo disponible en el iPhone), pero indicando al formateador que quiero generar números decimales. Tenga en cuenta que he simplificado mi ejemplo (en realidad estoy tratando con cadenas de moneda). Sin embargo, obviamente estoy haciendo algo mal para que devuelva un valor que ilustre la imprecisión de los números de coma flotante.

¿Cuál es el método correcto para convertir un string de número específico de locale a un NSDecimalNumber?

Editar: Tenga en cuenta que mi ejemplo es por simplicidad. La pregunta que hago también debe relacionarse con cuándo necesita tomar una cadena monetaria específica de la configuración regional y convertirla a un NSDecimalNumber. Además, esto se puede expandir a una cadena de porcentaje específico de la configuración regional y convertirlo a NSDecimalNumber.

Respuesta

13

Según la respuesta de Boaz Stuller, he registrado un error en Apple por este problema. Hasta que eso se resuelva, aquí están las soluciones provisionales que he decidido como el mejor enfoque a seguir. Estas soluciones se basan simplemente en redondear el número decimal a la precisión adecuada, que es un enfoque simple que puede complementar su código existente (en lugar de cambiar de formateadores a escáneres).

números generales

Esencialmente, sólo estoy redondeando el número basado en reglas que tienen sentido para mi situación. Entonces, YMMV dependiendo de la precisión que soporte.

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 
[formatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; 
[formatter setGeneratesDecimalNumbers:TRUE]; 

NSString *s = @"0.07"; 

// Create your desired rounding behavior that is appropriate for your situation 
NSDecimalNumberHandler *roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:2 raiseOnExactness:FALSE raiseOnOverflow:TRUE raiseOnUnderflow:TRUE raiseOnDivideByZero:TRUE]; 

NSDecimalNumber *decimalNumber = [formatter numberFromString:s]; 
NSDecimalNumber *roundedDecimalNumber = [decimalNumber decimalNumberByRoundingAccordingToBehavior:roundingBehavior]; 

NSLog([decimalNumber stringValue]); // prints 0.07000000000000001 
NSLog([roundedDecimalNumber stringValue]); // prints 0.07 

Monedas

Manejo de monedas (que es el problema real que estoy tratando de resolver) es sólo una ligera variación en el manejo de los números generales. La clave es que la escala del comportamiento de redondeo está determinada por los dígitos fraccionarios máximos utilizados por la moneda de la configuración regional.

NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init]; 
[currencyFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; 
[currencyFormatter setGeneratesDecimalNumbers:TRUE]; 
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; 

// Here is the key: use the maximum fractional digits of the currency as the scale 
int currencyScale = [currencyFormatter maximumFractionDigits]; 

NSDecimalNumberHandler *roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:currencyScale raiseOnExactness:FALSE raiseOnOverflow:TRUE raiseOnUnderflow:TRUE raiseOnDivideByZero:TRUE]; 

// image s is some locale specific currency string (eg, $0.07 or €0.07) 
NSDecimalNumber *decimalNumber = (NSDecimalNumber*)[currencyFormatter numberFromString:s]; 
NSDecimalNumber *roundedDecimalNumber = [decimalNumber decimalNumberByRoundingAccordingToBehavior:roundingBehavior]; 

NSLog([decimalNumber stringValue]); // prints 0.07000000000000001 
NSLog([roundedDecimalNumber stringValue]); // prints 0.07 
+1

Como nota al margen, debe convertir el número del formateador en un puntero a NSDecimalNumber. Es decir. NSDecimalNumber * decimalNumber = (NSDecimalNumber *) [formatter numberFromString: s]; –

3

Esto parece funcionar:

NSString *s = @"0.07"; 

NSScanner* scanner = [NSScanner localizedScannerWithString:s]; 
NSDecimal decimal; 
[scanner scanDecimal:&decimal]; 
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithDecimal:decimal]; 

NSLog([decimalNumber stringValue]); // prints 0.07 

También, abra una incidencia en esto. Definitivamente ese no es el comportamiento correcto que estás viendo allí.

Editar: Hasta que Apple corrija esto (y luego todas las actualizaciones de usuarios potenciales a la versión fija de OSX), probablemente tendrá que pasar su propio analizador usando NSScanner o aceptar 'solo' la precisión doble para los números ingresados. A menos que planee tener el presupuesto del Pentágono en esta aplicación, sugeriría lo último. De manera realista, los dobles tienen una precisión de 14 decimales, por lo que a menos de un billón de dólares, tendrán menos de un centavo. Tuve que escribir mis propias rutinas de análisis de fechas basadas en NSDateFormatter para un proyecto y pasé literalmente un mes manejando todos los casos divertidos (como que solo Suecia tiene el día de la semana incluido en su fecha larga).

+0

Error # 6400434 registrado. Gracias por la respuesta. Como mencioné, mi ejemplo fue simplificado (en realidad estoy lidiando con monedas). Entonces, no estoy seguro de que esta respuesta funcione para este escenario. – shek

+0

Boaz Stuller: vea mi respuesta que ilustra un fragmento de código que maneja este problema de manera bastante simple, sin usar un analizador sintáctico/NSScanner personalizado ... y bien bajo un presupuesto del Pentágono. ;) – shek

0

Consulte también Best way to store currency values in C++

La mejor manera de manejar la moneda es el uso de un valor entero para la unidad más pequeña de la moneda, es decir, para centavos dólares/euros, etc.Evitará cualquier error de precisión relacionado con coma flotante en su código.

Teniendo esto en cuenta, la mejor manera de analizar cadenas que contienen un valor de moneda es hacerlo manualmente (con un carácter de punto decimal configurable). Divida la cadena en el punto decimal y analice la primera y la segunda parte como valores enteros. Luego usa construir tu valor combinado a partir de esos.

+0

Gracias por su respuesta, unwesen. Sus comentarios son una buena regla empírica para manejar monedas (que ya estoy haciendo). Sin embargo, en lugar de analizar manualmente la cadena de moneda de entrada como describió, consulte los ejemplos de mi código en mi respuesta que manejan este problema de manera más elegante. – shek

+0

Específicamente, mi ejemplo de código demuestra el manejo de las entradas de cadena de moneda en cualquier localidad. Esto es mucho más fácil que tener que analizar manualmente la cadena y la cuenta para las diversas reglas locales específicas sobre agrupación, separadores de decimales, símbolos de moneda, etc. – shek

18

años más tarde:

+(NSDecimalNumber *)decimalNumberWithString:(NSString *)numericString en NSDecimalNumber.

+2

Tenga cuidado con el separador decimal diferente en algunas configuraciones regionales. Por ejemplo, en francés usamos la coma "," para separar los decimales. Esta línea no se convertirá correctamente en ese caso. '[NSDecimalNumber decimalNumberWithString: string locale: [NSLocale currentLocale]]' funciona bien, sin embargo :) – aspyct

Cuestiones relacionadas