7

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

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

NSString *s = [object cents:n];

而且我正在尝试制作一种方法,该方法将返回 01、02 等...最多 99 作为字符串。我似乎无法弄清楚如何以这种格式将尾数作为字符串返回。我觉得我错过了一种方便的方法,但我再次查看了文档,并没有看到任何突然出现在我身上的东西。

4

5 回答 5

11

更新:这是一个相对较旧的答案,但看起来人们仍在寻找它。我想更新这个以确保正确性——我最初回答这个问题只是为了演示如何从 a 中提取特定的小数位double,但我不提倡将其作为表示货币信息的一种方式。

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

如果您以整数美分的形式存储美元值,那么对于大多数操作,您将永远不会遇到浮点错误,并且将美分转换为美元进行格式化非常容易。例如,如果您需要征税,或者将您的美分值乘以 a double,则执行此操作以获得一个double值,应用银行家的四舍五入四舍五入到最接近的美分,然后转换回整数。

如果您尝试支持多种不同的货币,情况会变得更加复杂,但有一些方法可以解决这个问题。

tl;dr不要使用浮点数来表示货币,你会更快乐,更正确。NSDecimalNumber能够准确(并且精确地)表示十进制值,但是一旦转换为double/ float,您就有引入浮点错误的风险。


这可以相对容易地完成:

  1. 获取double十进制数的值(假设数字不是太大而无法存储在双精度数中,这将起作用)。
  2. 在单独的变量中,将double转换为整数。
  3. 将这两个数字乘以 100 以考虑精度损失(本质上,转换为美分)并从原始数字中减去美元以获得美分数。
  4. 以字符串形式返回。

(这是处理所有十进制数的通用公式——处理NSDecimalNumber只需要一点胶水代码就可以工作)。

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

- (NSString *)cents {
    double value = [self doubleValue];
    unsigned dollars = (unsigned)value;
    unsigned cents = (value * 100) - (dollars * 100);

    return [NSString stringWithFormat:@"%02u", cents];
}
于 2011-03-18T18:44:51.217 回答
11

这是一个更安全的实现,它适用于不适合 a 的值double

@implementation NSDecimalNumber (centsStringAddition)

- (NSString*) 美分字符串;
{
    NSDecimal value = [self decimalValue];

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

    NSDecimal 美分;
    NSDecimalSubtract(¢s, &value, &dollars, NSRoundPlain);

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

@结尾

这打印01

    NSDecimalNumber* dn = [[NSDecimalNumber alloc] initWithString:@"123456789012345678901234567890.0108"];
    NSLog(@"%@", [dn centsString]);
于 2011-08-12T08:13:19.007 回答
2

我不同意 Itai Ferber 的方法,因为使用 ofdoubleValue方法NSNumber会导致精度损失,例如 1.60 >> 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];
}
于 2013-03-02T11:17:13.980 回答
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")
    }
}
于 2016-03-07T21:15:26.957 回答
1

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

例如:

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

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

于 2011-08-12T07:21:18.850 回答