xcode NSDecimalNumber 的小数部分

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5355755/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 20:33:46  来源:igfitidea点击:

Fractional Part of NSDecimalNumber

objective-cxcodensdecimalnumbermantissa

提问by JamesB41

I'm using NSDecimalNumber to store a value for currency. I'm trying to write a method called "cents" which returns the decimal portion of the number as an NSString with a leading 0 if the number is < 10. So basically

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

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

NSString *s = [object cents:n];

And I'm trying to craft a method that will return 01, 02, etc...up to 99 as a string. I can't seem to figure out how to return the mantissa as a string in this format. I feel like I'm missing a convenience method but I looked at the documentation again and don't see anything jumping out at me.

而且我正在尝试制作一种方法,该方法将返回 01、02 等......高达 99 作为字符串。我似乎无法弄清楚如何以这种格式将尾数作为字符串返回。我觉得我错过了一个方便的方法,但我再次查看了文档,没有看到任何内容跳出来。

回答by Itai Ferber

Update:This is a relatively old answer, but it looks like people are still finding it. I want to update this for correctness — I originally answered the question as I did simply to demonstrate how one could pull out specific decimal places from a double, but I do not advocate this as a way to represent currency information.

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

Neveruse floating-point numbers to represent currency information. As soon as you start dealing with decimal numbers (as you would with dollars with cents), you introduce possible floating point errors into your code. A computer cannot represent all decimal values accurately (1/100.0, for instance, 1 cent, is represented as 0.01000000000000000020816681711721685132943093776702880859375on my machine). Depending on which currencies you plan on representing, it is always more correct to store a quantity in terms of its base amount (in this case, cents).

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

If you store your dollar values in terms of integral cents, you'll never run into floating-point errors for most operations, and it's trivially easy to convert cents into dollars for formatting. If you need to apply tax, for instance, or multiply your cents value by a double, do that to get a doublevalue, apply banker's roundingto round to the nearest cent, and convert back to an integer.

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

It gets more complicated than that if you're trying to support multiple different currencies, but there are ways of dealing with that.

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

tl;drDon't use floating-point numbers to represent currency and you'll be much happier, and more correct. NSDecimalNumberis able to accurately (and precisely) represent decimal values, but as soon as you convert to double/float, you run the risk of introducing floating-point errors.

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



This can be done relatively easily:

这可以相对容易地完成:

  1. Get the doublevalue of the decimal number (this will work assuming the number is not too large to store in a double).
  2. In a separate variable, cast the doubleto an integer.
  3. Multiply both numbers by 100 to account for loss of precision (essentially, convert to cents) and subtract dollars from the original to get the number of cents.
  4. Return in a string.
  1. 获取double十进制数的值(假设数字不是太大而无法存储在双精度数中,这将起作用)。
  2. 在单独的变量中,将double转换为整数。
  3. 将两个数字乘以 100 以考虑精度损失(本质上,转换为美分)并从原始数字中减去美元以获得美分的数量。
  4. 以字符串形式返回。

(This is a general formula for working with all decimal numbers – working with NSDecimalNumberjust requires a little bit of glue code to get it to work).

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

In practice, it would look like this (in an NSDecimalNumbercategory):

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

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

    return [NSString stringWithFormat:@"%02u", cents];
}

回答by hamstergene

This is a safer implementation which will work with values that do not fit into a double:

这是一个更安全的实现,它适用于不适合的值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

This prints 01:

这打印01

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

回答by Alex Rush

I don't agree with Itai Ferber's method, because using of doubleValuemethod of NSNumbercan cause lost of precision like 1.60 >> 1.5999999999, so your cents value will be "59". Instead I cut "dollars"/"cents" from string, obtained with NSNumberFormatter:

我不同意 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];
}

回答by Graham Perks

Swift version, with bonus function for obtaining just the dollar amount too.

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"
    }
}

Tests:

测试:

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")
    }
}

回答by d.ennis

I have a different approach. It Works for me but I am not sure if it might cause any problems? I let NSDecimalNumber return the value directly by returning an integer value...

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

e.G.:

例如:

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

Would this approach be more expensive since I am converting NSDecimalNumber two times?

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