iphone - español - Obteniendo un NSDecimalNumber de una cadena local específica?
cocoa touch español (4)
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
Además, archiva un error 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'' doble precisión 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).
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. De los ejemplos que he visto hasta ahora en las interwebs , esto se logra usando un NSNumberFormatter a la:
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 10.4 (además de ser recomendado según 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 una cadena numérica específica de la configuración regional en 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 en un NSDecimalNumber. Además, esto se puede expandir a una cadena de porcentaje específico de la configuración regional y convertirlo a NSDecimalNumber.
Años después:
+(NSDecimalNumber *)decimalNumberWithString:(NSString *)numericString
en NSDecimalNumber
.
Basado en la respuesta de Boaz Stuller, registré un error en Apple para 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
Básicamente, estoy redondeando el número según las 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
Manejar las monedas (que es el problema real que trato de resolver) es solo una pequeña 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
Consulte también La mejor forma de almacenar valores de moneda en C ++
La mejor forma de manejar la moneda es usar un valor entero para la unidad más pequeña de la moneda, es decir, centavos por 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.