Ответ 1
Вы были на правильном пути (с оговорками, см. ниже). Вы должны просто использовать инициализаторы из NSNumber, поскольку они наследуются NSDecimalNumber.
NSDecimalNumber *floatDecimal = [[[NSDecimalNumber alloc] initWithFloat:42.13f] autorelease];
NSDecimalNumber *doubleDecimal = [[[NSDecimalNumber alloc] initWithDouble:53.1234] autorelease];
NSDecimalNumber *intDecimal = [[[NSDecimalNumber alloc] initWithInt:53] autorelease];
NSLog(@"floatDecimal floatValue=%6.3f", [floatDecimal floatValue]);
NSLog(@"doubleDecimal doubleValue=%6.3f", [doubleDecimal doubleValue]);
NSLog(@"intDecimal intValue=%d", [intDecimal intValue]);
Более подробную информацию по этому вопросу можно найти здесь.
Тем не менее, я видел много дискуссий о переполнении стека и в Интернете о проблемах с инициализацией NSDecimalNumber. Кажется, что есть некоторые проблемы, связанные с точностью и преобразованием в/из парных с помощью NSDecimalNumber
. Особенно, если вы используете элементы инициализации, унаследованные от NSNumber.
Я выбил тест ниже:
double dbl = 36.76662445068359375000;
id xx1 = [NSDecimalNumber numberWithDouble: dbl]; // Don't do this
id xx2 = [[[NSDecimalNumber alloc] initWithDouble: dbl] autorelease];
id xx3 = [NSDecimalNumber decimalNumberWithString:@"36.76662445068359375000"];
id xx4 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L exponent:-17 isNegative:NO];
NSLog(@"raw doubleValue: %.20f, %.17f", dbl, dbl);
NSLog(@"xx1 doubleValue: %.20f, description: %@", [xx1 doubleValue], xx1);
NSLog(@"xx2 doubleValue: %.20f, description: %@", [xx2 doubleValue], xx2);
NSLog(@"xx3 doubleValue: %.20f, description: %@", [xx3 doubleValue], xx3);
NSLog(@"xx4 doubleValue: %.20f, description: %@", [xx4 doubleValue], xx4);
Вывод:
raw doubleValue: 36.76662445068359375000 36.76662445068359375
xx1 doubleValue: 36.76662445068357953915, description: 36.76662445068359168
xx2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168
xx3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375
xx4 doubleValue: 36.76662445068357953915, description: 36.76662445068359375
Таким образом, вы можете видеть, что при использовании метода numberWithDouble
удобства на NSNumber
(который вы действительно не должны использовать из-за того, что он возвращает неправильный тип указателя) и даже инициализатор initWithDouble
(этот IMO должен " ОК), вы получите NSDecimalNumber с внутренним представлением (возвращаемым при вызове description
на объекте), который не такой точный, как тот, который вы получаете при вызове decimalNumberWithString:
или decimalNumberWithMantissa:exponent:isNegative:
.
Также обратите внимание, что преобразование обратно в double путем вызова doubleValue
в экземпляре NSDecimalNumber
приводит к потере точности, но, что интересно, то же самое независимо от того, какой инициализатор вы вызываете.
В конце концов, я думаю, что рекомендуется использовать один из методов decimalNumberWith*
, объявленных на уровне класса NSDecimalNumber
, для создания экземпляров NSDecimalNumber при работе с высокоточными числами с плавающей запятой.
Итак, как вы называете эти инициализаторы легко, когда у вас есть двойной, плавающий или другой NSNumber?
Два метода, описанные здесь "работают", но все еще имеют проблемы с точностью.
Если у вас уже есть номер, сохраненный как NSNumber, тогда это должно работать:
id n = [NSNumber numberWithDouble:dbl];
id dn1 = [NSDecimalNumber decimalNumberWithDecimal:[n decimalValue]];
NSLog(@" n doubleValue: %.20f, description: %@", [n doubleValue], n);
NSLog(@"dn1 doubleValue: %.20f, description: %@", [dn1 doubleValue], dn1);
Но, как вы можете видеть на выходе ниже, он отбрасывает некоторые из менее значимых цифр:
n doubleValue: 36.76662445068359375000, description: 36.76662445068359
dn1 doubleValue: 36.76662445068357953915, description: 36.76662445068359
Если число является примитивным (float или double), то это должно работать:
id dn2 = [NSDecimalNumber decimalNumberWithMantissa:(dbl * pow(10, 17))
exponent:-17
isNegative:(dbl < 0 ? YES : NO)];
NSLog(@"dn2 doubleValue: %.20f, description: %@", [dn2 doubleValue], dn2);
Но вы снова получите ошибки точности. Как вы можете видеть на следующем рисунке:
dn2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168
Причина, по которой я думаю, что это прецизионная потеря связана с вовлечением многоточия с плавающей запятой, потому что следующий код работает нормально:
id dn3 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L
exponent:-17
isNegative:NO];
NSLog(@"dn3 doubleValue: %.20f, description: %@", [dn3 doubleValue], dn3);
Вывод:
dn3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375
Таким образом, наиболее последовательное точное преобразование/инициализация из double
или float
в NSDecimalNumber использует decimalNumberWithString:
. Но, как отметил Брэд Ларсон в своем ответе, это может быть немного медленным. Его метод преобразования с использованием NSScanner может быть лучше, если производительность становится проблемой.