我需要转换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
的初始值是什么,我怎么能以最准确的方式对这个数字进行舍入?如果存在更准确的方法来进行初始计算,那该方法是什么?
答案 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;
}