将double转换为NSDecimalNumber,同时保持准确性

时间:2014-12-25 15:17:49

标签: ios objective-c double nsdecimalnumber

我需要转换double中执行的计算结果,但我不能使用decimalNumberByMultiplyingBy或任何其他NSDecimalNumber函数。我试图通过以下方式获得准确的结果:

double calc1 = 23.5 * 45.6 * 52.7;  // <-- Correct answer is 56473.32
NSLog(@"calc1 = %.20f", calc1);

- &GT; calc1 = 56473.32000000000698491931

NSDecimalNumber *calcDN = (NSDecimalNumber *)[NSDecimalNumber numberWithDouble:calc1];
NSLog(@"calcDN = %@", [calcDN stringValue]);

- &GT; calcDN = 56473.32000000001024

NSDecimalNumber *testDN = [[[NSDecimalNumber decimalNumberWithString:@"23.5"] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithString:@"45.6"]] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithString:@"52.7"]];
NSLog(@"testDN = %@", [testDN stringValue]);

- &GT; testDN = 56473.32

我知道这种差异与各自的准确性有关。

但这是我的问题:无论double的初始值是什么,我怎么能以最准确的方式对这个数字进行舍入?如果存在更准确的方法来进行初始计算,那该方法是什么?

2 个答案:

答案 0 :(得分:4)

好吧,您可以使用double来表示数字并接受不准确或使用某些不同的数字表示,例如NSDecimalNumber。这完全取决于与准确性有关的预期值和业务要求。

如果确实至关重要,不使用NSDecimalNumber提供的算术方法,那么使用NSDecimalNumberHandler最好控制舍入行为,这是{{1}的具体实现。 1}}协议。使用NSDecimalNumberBehaviors方法执行实际舍入。

这是片段 - 它在Swift中,但它应该是可读的:

decimalNumberByRoundingAccordingToBehavior:

在使用let behavior = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundPlain, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) let calcDN : NSDecimalNumber = NSDecimalNumber(double: calc1) .decimalNumberByRoundingAccordingToBehavior(behavior) calcDN.stringValue // "56473.32" 表示时,我不知道任何提高实际计算准确性的方法。

答案 1 :(得分:3)

我建议根据double中的位数舍入数字,以便截断NSDecimalNumber只显示相应的位数,从而消除潜在错误形成的数字,例如:

// Get the number of decimal digits in the double
int digits = [self countDigits:calc1];

// Round based on the number of decimal digits in the double
NSDecimalNumberHandler *behavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundDown scale:digits raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO];
NSDecimalNumber *calcDN = (NSDecimalNumber *)[NSDecimalNumber numberWithDouble:calc1];
calcDN = [calcDN decimalNumberByRoundingAccordingToBehavior:behavior];

我已经调整了this answer中的countDigits:方法:

- (int)countDigits:(double)num {
    int rv = 0;
    const double insignificantDigit = 18; // <-- since you want 18 significant digits
    double intpart, fracpart;
    fracpart = modf(num, &intpart); // <-- Breaks num into an integral and a fractional part.

    // While the fractional part is greater than 0.0000001f,
    // multiply it by 10 and count each iteration
    while ((fabs(fracpart) > 0.0000001f) && (rv < insignificantDigit)) {
        num *= 10;
        fracpart = modf(num, &intpart);
        rv++;
    }
    return rv;
}