Obtendo um NSDecimalNumber de uma seqüência específica de localidade?

Eu tenho algumas cadeias de caracteres que são específicas da localidade (por exemplo, 0,01 ou 0,01). Eu quero converter essa string para um NSDecimalNumber. Dos exemplos que vi até agora nas interwebs , isso é feito usando um 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 

Estou usando o modo 10.4 (além de ser recomendado pela documentação, também é o único modo disponível no iPhone), mas indicando ao formatador que quero gerar números decimais. Observe que simplifiquei meu exemplo (na verdade, estou lidando com sequências de moeda). No entanto, estou obviamente fazendo algo errado para retornar um valor que ilustra a imprecisão dos números de ponto flutuante.

Qual é o método correto para converter uma seqüência numérica específica de localidade em um NSDecimalNumber?

Edit: Note que o meu exemplo é para simplificar. A pergunta que estou fazendo também deve estar relacionada a quando você precisa obter uma string de moeda específica de localidade e convertê-la em um NSDecimalNumber. Além disso, isso pode ser expandido para uma string de porcentagem específica de localidade e convertê-lo em um NSDecimalNumber.

Anos depois:

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

Com base na resposta de Boaz Stuller, registrei um bug para a Apple para esse problema. Até que isso seja resolvido, aqui estão as soluções alternativas que decidi como a melhor abordagem a seguir. Essas soluções simplesmente dependem do arredondamento do número decimal para a precisão apropriada, que é uma abordagem simples que pode complementar seu código existente (em vez de alternar de formatadores para scanners).

Números Gerais

Essencialmente, estou apenas arredondando o número com base em regras que fazem sentido para a minha situação. Então, YMMV, dependendo da precisão que você suporta.

 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 

Moedas

O manuseio de moedas (que é o problema real que estou tentando resolver) é apenas uma ligeira variação no manuseio de números gerais. A chave é que a escala do comportamento de arredondamento é determinada pelos dígitos fracionários máximos usados ​​pela moeda do local.

 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 

Isso 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 

Além disso, arquive um bug sobre isso. Esse definitivamente não é o comportamento correto que você está vendo lá.

Edit: Até que a Apple conserte isso (e então todas as atualizações potenciais do usuário para a versão OSX fixa), você provavelmente terá que rolar seu próprio analisador usando o NSScanner ou aceitar a precisão dupla ‘apenas’ para os números typescripts. A menos que você esteja planejando ter o orçamento do Pentágono neste aplicativo, eu sugiro o último. Realisticamente, as duplas têm precisão de 14 casas decimais, portanto, a menos de um trilhão de dólares, elas terão menos de um centavo. Eu tive que escrever minhas próprias rotinas de análise de datas com base em NSDateFormatter para um projeto e passei literalmente um mês lidando com todos os casos engraçados (como o único dia em que a Suécia tem o dia da semana).

Veja também Melhor maneira de armazenar valores monetários em C ++

A melhor maneira de lidar com a moeda é usar um valor inteiro para a menor unidade da moeda, ou seja, centavos para dólares / euros, etc. Você evitará erros de precisão relacionados ao ponto flutuante em seu código.

Com isso em mente, a melhor maneira de analisar cadeias contendo um valor de moeda é fazer isso manualmente (com um caractere de ponto decimal configurável). Divida a string no ponto decimal e analise a primeira e a segunda parte como valores inteiros. Em seguida, use construir seu valor combinado daqueles.