Подтвердить что ты не робот

Правильный способ создания экземпляра NSDecimalNumber из float или double

Я ищу лучший способ создать экземпляр NSDecimalNumber из двойного или короткого. Существуют следующие методы и методы NSNumber...

+NSNumber numberWithFloat
+NSNumber numberWithDouble
-NSNumber initWithFloat
-NSNumber initWithDouble

но они возвращают NSNumber. С другой стороны, NSDecimalNumber определяет следующее:

+NSDecimalNumber decimalNumberWithMantissa:exponent:isNegative:
+NSDecimalNumber decimalNumberWithDecimal:

Здесь есть несколько возможностей, по которым идти. Xcode генерирует предупреждение, если у вас есть NSDecimalNumber, установленное для возвращаемого значения вышеперечисленных методов NSNumber.

Поблагодарили бы за ввод самого чистого и правильного пути...

4b9b3361

Ответ 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 может быть лучше, если производительность становится проблемой.

Ответ 2

Есть некоторые проблемы, которые могут возникнуть, если вы используете инициализаторы NSNumber при создании NSDecimalNumber. См. Пример, опубликованный в этом вопросе для отличного случая. Кроме того, я доверяю словам Билла Бумгарнера об этом от его ответ в списке рассылки cocoa -dev. См. Также Эшли ответить здесь.

Поскольку я параноик об этих проблемах преобразования, я использовал следующий код в прошлом для создания NSDecimal structs:

NSDecimal decimalValueForDouble(double doubleValue)
{
    NSDecimal result;
    NSString *stringRepresentationOfDouble = [[NSString alloc] initWithFormat:@"%f", doubleValue];
    NSScanner *theScanner = [[NSScanner alloc] initWithString:stringRepresentationOfDouble];
    [stringRepresentationOfDouble release];

    [theScanner scanDecimal:&result];
    [theScanner release];

    return result;
}

Причиной сложного кода здесь является то, что я обнаружил, что NSScanner примерно на 90% быстрее, чем инициализация NSDecimalNumber из строки. Я использую NSDecimal structs, потому что они могут приносить значительные преимущества производительности своим братьям NSDecimalNumber.

Для более простого, но немного более медленного подхода NSDecimalNumber вы можете использовать что-то вроде

NSDecimalNumber *decimalNumberForDouble(double doubleValue)
{
    NSString *stringRepresentationOfDouble = [[NSString alloc] initWithFormat:@"%f", doubleValue];
    NSDecimalNumber *stringProcessor = [[NSDecimalNumber alloc] initWithString:stringRepresentationOfDouble];
    [stringRepresentationOfDouble release];
    return [stringProcessor autorelease];
}

Однако, если вы работаете с NSDecimalNumbers, вы захотите сделать все возможное, чтобы избежать приведения значения в число с плавающей точкой в ​​любой точке. Считайте значения строк из текстовых полей и преобразуйте их непосредственно в NSDecimalNumbers, выполните свою математику с NSDecimalNumbers и выпишите их на диск как NSStrings или десятичные значения в Core Data. Вы начинаете вводить ошибки представления с плавающей запятой, как только ваши значения передаются в числа с плавающей запятой в любой точке.

Ответ 3

Вы можете попробовать:

float myFloat = 42.0;
NSDecimal myFloatDecimal = [[NSNumber numberWithFloat:myFloat] decimalValue];
NSDecimalNumber* num = [NSDecimalNumber decimalNumberWithDecimal:myFloatDecimal];

По-видимому, есть более быстрые способы выполнения этого, но, возможно, это не стоит, если код не является узким местом.

Ответ 4

-[NSNumber initWithFloat:] и -[NSNumber initWithDouble:] фактически не возвращают объекты NSNumber *, они возвращают id и NSDecimalNumber наследуют от NSNumber, поэтому вы можете использовать:

NSDecimalNumber *decimal = [[[NSDecimalNumber alloc] initWithFloat:1.1618] autorelease];

И я понял, что то же самое касается +numberWithFloat:, поэтому вы можете просто использовать это.

Ответ 5

Или может быть вы можете определить макрос, чтобы сократить код

#define DECIMAL(x) [[NSDecimalNumber alloc]initWithDouble:x]

budget.amount = DECIMAL(5000.00);