Получение NSDecimalNumber из определенной строки локали? - PullRequest
15 голосов
/ 25 ноября 2008

У меня есть строка s, специфичная для локали (например, 0,01 или 0,01). Я хочу преобразовать эту строку в NSDecimalNumber. Из примеров, которые я до сих пор видел на межсетях , это достигается с помощью NSNumberFormatter а-ля:

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

Я использую режим 10.4 (помимо того, что рекомендуется в документации, это также единственный режим, доступный на iPhone), но указывающий форматировщику, что я хочу генерировать десятичные числа. Обратите внимание, что я упростил мой пример (на самом деле я имею дело со строками валюты). Однако я, очевидно, что-то делаю не так, чтобы вернуть значение, иллюстрирующее неточность чисел с плавающей запятой.

Как правильно преобразовать строку номера, специфичную для локали, в NSDecimalNumber?

Изменить: Обратите внимание, что мой пример для простоты. Вопрос, который я задаю, также должен касаться того, когда вам нужно взять строку валюты определенной локали и преобразовать ее в NSDecimalNumber. Кроме того, это может быть расширено до конкретной процентной строки локали и преобразовано в NSDecimalNumber.

Ответы [ 4 ]

18 голосов
/ 20 мая 2011

лет спустя:

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

13 голосов
/ 26 ноября 2008

Основываясь на ответе Боаза Стуллера, я зарегистрировал в Apple ошибку для этой проблемы. Пока это не решено, вот обходные пути, которые я выбрал как лучший подход. Эти обходные пути просто основаны на округлении десятичного числа до необходимой точности, что является простым подходом, который может дополнить существующий код (вместо переключения с форматеров на сканеры).

Общие номера

По сути, я просто округляю число на основе правил, которые имеют смысл для моей ситуации. Итак, YMMV в зависимости от точности, которую вы поддерживаете.

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

Валюта

Работа с валютами (что является реальной проблемой, которую я пытаюсь решить) - это лишь небольшая вариация в обработке общих чисел. Ключ заключается в том, что масштаб поведения округления определяется максимальными дробными цифрами, используемыми валютой локали.

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
3 голосов
/ 25 ноября 2008

Это похоже на работу:

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

Также сообщите об ошибке. Это определенно неправильное поведение, которое вы там видите.

Редактировать: До тех пор, пока Apple не исправит это (а затем каждый потенциальный пользователь не обновит до фиксированной версии OSX), вам, вероятно, придется свернуть свой собственный анализатор с помощью NSScanner или принять «только» двойную точность для введенных чисел. Если вы не планируете использовать бюджет Пентагона в этом приложении, я бы предложил последнее. Реально, двойные с точностью до 14 десятичных знаков, так что при цене менее триллиона долларов они будут меньше, чем на пенни. Я должен был написать свои собственные процедуры анализа дат на основе NSDateFormatter для проекта, и я потратил буквально месяц на обработку всех забавных крайних случаев (например, как только в Швеции день недели включен в его длинную дату).

0 голосов
/ 26 ноября 2008

См. Также Лучший способ хранения валютных значений в C ++

Лучший способ обработки валюты - использовать целочисленное значение для наименьшей единицы валюты, т. Е. Центы для долларов / евро и т. Д. Вы избежите любых ошибок точности, связанных с плавающей запятой, в своем коде.

Учитывая это, лучший способ анализа строк, содержащих значение валюты, - это сделать это вручную (с настраиваемым символом десятичной точки). Разбейте строку в десятичной точке и проанализируйте первую и вторую части как целочисленные значения. Затем используйте построить ваше объединенное значение из тех.

...