NSDecimalNumber的小数部分

时间:2011-03-18 17:34:51

标签: objective-c xcode nsdecimalnumber mantissa

我正在使用NSDecimalNumber存储货币值。我正在尝试编写一个名为“cents”的方法,该方法将数字的小数部分作为NSString返回,如果数字为< 10.所以基本上

NSDecimalNumber *n = [[NSDecimalNumber alloc] initWithString:@"1234.55"];

NSString *s = [object cents:n];

我正在尝试制作一种方法,它将返回01,02等......最多99作为字符串。我似乎无法弄清楚如何将尾数作为这种格式的字符串返回。我觉得我错过了一种方便的方法,但我再次看了一下文档,看不到任何东西向我发出。

5 个答案:

答案 0 :(得分:11)

更新:这是一个相对陈旧的答案,但看起来人们仍然在寻找它。我想更新这个以获得正确性 - 我最初回答了这个问题,因为我只是为了演示如何从double中提取特定的小数位,但我并不主张将其作为表示货币信息的方式。< / p>

从不使用浮点数来表示货币信息。一旦开始处理十进制数字(就像你用美元一样处理美分),就会在代码中引入可能的浮点错误。计算机无法准确表示所有小数值({1}},例如1美分,在我的机器上表示为1/100.0。根据您计划代表的货币,以基本金额(在本例中为美分)存储数量总是更正确。

如果以积分美分存储您的美元价值,您绝不会遇到大多数操作的浮点错误,并且很容易将美分兑换成美元进行格式化。例如,如果您需要应用税金,或者将您的美分值乘以0.01000000000000000020816681711721685132943093776702880859375,请执行此操作以获得double值,应用banker's rounding舍入到最接近的分数,然后转换回到整数。

如果你试图支持多种不同的货币,它会变得更加复杂,但有办法解决这个问题。

tl; dr 不要使用浮点数来表示货币,你会更开心,更正确。 double能够准确(并且精确地)表示十进制值,但只要转换为NSDecimalNumber / double,就会产生引入浮点错误的风险。


这可以相对容易地完成:

  1. 获取十进制数的float值(假设数字不是太大而无法存储在一个双数中,这将有效。)
  2. 在单独的变量中,将double强制转换为整数。
  3. 将两个数字乘以100以说明精度损失(实质上是转换为美分)并从原始数字中扣除美元以获得分数。
  4. 以字符串形式返回。
  5. (这是使用所有十进制数的通用公式 - 使用double只需要一些胶水代码即可使其工作)。

    在实践中,它看起来像这样(在NSDecimalNumber类别中):

    NSDecimalNumber

答案 1 :(得分:11)

这是一个更安全的实现,可以使用不适合double的值:

@implementation NSDecimalNumber (centsStringAddition)

- (NSString*) centsString;
{
    NSDecimal value = [self decimalValue];

    NSDecimal dollars;
    NSDecimalRound(&dollars, &value, 0, NSRoundPlain);

    NSDecimal cents;
    NSDecimalSubtract(&cents, &value, &dollars, NSRoundPlain);

    double dv = [[[NSDecimalNumber decimalNumberWithDecimal:cents] decimalNumberByMultiplyingByPowerOf10:2] doubleValue];
    return [NSString stringWithFormat:@"%02d", (int)dv];
}

@end

这会打印01

    NSDecimalNumber* dn = [[NSDecimalNumber alloc] initWithString:@"123456789012345678901234567890.0108"];
    NSLog(@"%@", [dn centsString]);

答案 2 :(得分:2)

我不同意Itai Ferber的方法,因为使用doubleValue NSNumber方法会导致精确度下降,如1.60&gt;&gt; 1.5999999999,所以你的分值将是“59”。相反,我用NSNumberFormatter获得的字符串中的“美元”/“美分”:

+(NSString*)getSeparatedTextForAmount:(NSNumber*)amount centsPart:(BOOL)cents
{
    static NSNumberFormatter* fmt2f = nil;
    if(!fmt2f){
        fmt2f = [[NSNumberFormatter alloc] init];
        [fmt2f setNumberStyle:NSNumberFormatterDecimalStyle];
        [fmt2f setMinimumFractionDigits:2]; // |
        [fmt2f setMaximumFractionDigits:2]; // | - exactly two digits for "money" value
    }
    NSString str2f = [fmt2f stringFromNumber:amount];
    NSRange r = [str2f rangeOfString:fmt2f.decimalSeparator];
    return cents ? [str2f substringFromIndex:r.location + 1] : [str2f substringToIndex:r.location];
}

答案 3 :(得分:2)

Swift版本,具有奖金功能,也可以获得美元金额。

extension NSDecimalNumber {

    /// Returns the dollars only, suitable for printing. Chops the cents.
    func dollarsOnlyString() -> String {
        let behaviour = NSDecimalNumberHandler(roundingMode:.RoundDown,
                                               scale: 0, raiseOnExactness: false,
                                               raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)

        let rounded = decimalNumberByRoundingAccordingToBehavior(behaviour)
        let formatter = NSNumberFormatter()
        formatter.numberStyle = .NoStyle
        let str = formatter.stringFromNumber(rounded)
        return str ?? "0"

    }

    /// Returns the cents only, e.g. "00" for no cents.
    func centsOnlyString() -> String {
        let behaviour = NSDecimalNumberHandler(roundingMode:.RoundDown,
                                               scale: 0, raiseOnExactness: false,
                                               raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)

        let rounded = decimalNumberByRoundingAccordingToBehavior(behaviour)
        let centsOnly = decimalNumberBySubtracting(rounded)
        let centsAsWholeNumber = centsOnly.decimalNumberByMultiplyingByPowerOf10(2)
        let formatter = NSNumberFormatter()
        formatter.numberStyle = .NoStyle
        formatter.formatWidth = 2
        formatter.paddingCharacter = "0"
        let str = formatter.stringFromNumber(centsAsWholeNumber)
        return str ?? "00"
    }
}

试验:

class NSDecimalNumberTests: XCTestCase {

    func testDollarsBreakdown() {
        var amount = NSDecimalNumber(mantissa: 12345, exponent: -2, isNegative: false)
        XCTAssertEqual(amount.dollarsOnlyString(), "123")
        XCTAssertEqual(amount.centsOnlyString(), "45")

        // Check doesn't round dollars up.
        amount = NSDecimalNumber(mantissa: 12365, exponent: -2, isNegative: false)
        XCTAssertEqual(amount.dollarsOnlyString(), "123")
        XCTAssertEqual(amount.centsOnlyString(), "65")

        // Check zeros
        amount = NSDecimalNumber(mantissa: 0, exponent: 0, isNegative: false)
        XCTAssertEqual(amount.dollarsOnlyString(), "0")
        XCTAssertEqual(amount.centsOnlyString(), "00")

        // Check padding
        amount = NSDecimalNumber(mantissa: 102, exponent: -2, isNegative: false)
        XCTAssertEqual(amount.dollarsOnlyString(), "1")
        XCTAssertEqual(amount.centsOnlyString(), "02")
    }
}

答案 4 :(得分:1)

我有不同的方法。它对我有用,但我不确定它是否会引起任何问题? 我让NSDecimalNumber通过返回一个整数值直接返回值...

e.G:

// n is the NSDecimalNumber from above 
NSInteger value = [n integerValue];
NSInteger cents = ([n doubleValue] *100) - (value *100);

由于我将NSDecimalNumber转换两次,这种方法会更昂贵吗?