Ottenere un NSDecimalNumber da una stringa specifica locale?

Ho alcune stringhe che sono specifiche della locale (ad es. 0,01 o 0,01). Voglio convertire questa stringa in un NSDecimalNumber. Dagli esempi che ho visto fino ad ora sull’interwebs , questo si ottiene 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 

Sto usando la modalità 10.4 (oltre ad essere consigliata per la documentazione, è anche l’unica modalità disponibile su iPhone) ma indica al formattatore che voglio generare numeri decimali. Nota che ho semplificato il mio esempio (in realtà mi occupo di stringhe di valuta). Tuttavia, sto ovviamente facendo qualcosa di sbagliato per restituire un valore che illustra l’imprecisione dei numeri in virgola mobile.

Qual è il metodo corretto per convertire una stringa numerica specifica della locale in un NSDecimalNumber?

Modifica: si noti che il mio esempio è per semplicità. La domanda che sto chiedendo dovrebbe riguardare anche quando è necessario prendere una stringa di valuta specifica della locale e convertirla in un NSDecimalNumber. Inoltre, è ansible estenderlo a una stringa percentuale specifica della locale e convertirla in NSDecimalNumber.

Anni dopo:

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

Sulla base della risposta di Boaz Stuller, ho riscontrato un bug su Apple per questo problema. Fino a quando non verrà risolto, ecco i rimedi che ho deciso di adottare come miglior approccio. Queste soluzioni alternative si basano semplicemente sull’arrotondamento del numero decimale alla precisione appropriata, che è un approccio semplice che può integrare il codice esistente (anziché passare dai formattatori agli scanner).

Numeri generali

Essenzialmente, sto arrotondando il numero basandomi su regole che hanno senso per la mia situazione. Quindi, YMMV a seconda della precisione che supporti.

 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 

valute

Gestire le valute (che è il vero problema che sto cercando di risolvere) è solo una leggera variazione sulla gestione dei numeri generali. La chiave è che la scala del comportamento di arrotondamento è determinata dalle cifre frazionarie massime utilizzate dalla valuta locale.

 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 

Questo sembra funzionare:

 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 

Inoltre, presenta un bug su questo. Questo non è sicuramente il comportamento corretto che stai vedendo lì.

Modifica: fino a quando Apple non risolverà questo (e quindi ogni potenziale utente aggiorna la versione OSX fissa), probabilmente dovrai eseguire il rollup del tuo parser utilizzando NSScanner o accettare “solo” una doppia precisione per i numeri inseriti. A meno che tu non abbia intenzione di avere il budget del Pentagono in questa app, suggerirei quest’ultimo. Realisticamente, i doppi sono precisi fino a 14 decimali, quindi a meno di un trilione di dollari, saranno meno di un centesimo di sconto. Ho dovuto scrivere le mie routine di parsing data basate su NSDateFormatter per un progetto e ho passato letteralmente un mese a gestire tutti i casi divertenti, (come solo la Svezia ha il giorno della settimana incluso nella sua lunga data).

Vedi anche Il modo migliore per memorizzare i valori di valuta in C ++

Il modo migliore per gestire la valuta consiste nell’utilizzare un valore intero per l’unità più piccola della valuta, ovvero centesimi di dollari / euro, ecc. Eviterai qualsiasi errore di precisione relativo al virgola mobile nel codice.

Con questo in mente, il modo migliore per analizzare stringhe contenenti un valore di valuta è farlo manualmente (con un carattere punto decimale configurabile). Dividere la stringa al punto decimale e analizzare sia la prima sia la seconda parte come valori interi. Quindi usa il tuo valore combinato da quelli.