我正在使用NSDecimalNumber存储货币值。我正在尝试编写一个名为“cents”的方法,该方法将数字的小数部分作为NSString返回,如果数字为< 10.所以基本上
NSDecimalNumber *n = [[NSDecimalNumber alloc] initWithString:@"1234.55"];
NSString *s = [object cents:n];
我正在尝试制作一种方法,它将返回01,02等......最多99作为字符串。我似乎无法弄清楚如何将尾数作为这种格式的字符串返回。我觉得我错过了一种方便的方法,但我再次看了一下文档,看不到任何东西向我发出。
答案 0 :(得分:11)
更新:这是一个相对陈旧的答案,但看起来人们仍然在寻找它。我想更新这个以获得正确性 - 我最初回答了这个问题,因为我只是为了演示如何从double
中提取特定的小数位,但我并不主张将其作为表示货币信息的方式。< / p>
从不使用浮点数来表示货币信息。一旦开始处理十进制数字(就像你用美元一样处理美分),就会在代码中引入可能的浮点错误。计算机无法准确表示所有小数值({1}},例如1美分,在我的机器上表示为1/100.0
。根据您计划代表的货币,以基本金额(在本例中为美分)存储数量总是更正确。
如果以积分美分存储您的美元价值,您绝不会遇到大多数操作的浮点错误,并且很容易将美分兑换成美元进行格式化。例如,如果您需要应用税金,或者将您的美分值乘以0.01000000000000000020816681711721685132943093776702880859375
,请执行此操作以获得double
值,应用banker's rounding舍入到最接近的分数,然后转换回到整数。
如果你试图支持多种不同的货币,它会变得更加复杂,但有办法解决这个问题。
tl; dr 不要使用浮点数来表示货币,你会更开心,更正确。 double
能够准确(并且精确地)表示十进制值,但只要转换为NSDecimalNumber
/ double
,就会产生引入浮点错误的风险。
这可以相对容易地完成:
float
值(假设数字不是太大而无法存储在一个双数中,这将有效。)double
强制转换为整数。(这是使用所有十进制数的通用公式 - 使用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(¢s, &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转换两次,这种方法会更昂贵吗?