Question

J'utilise NSDecimalNumber pour stocker une valeur de la monnaie. Je suis en train d'écrire une méthode appelée « cents », qui renvoie la partie décimale du nombre comme NSString avec un 0 si le nombre est <10. Donc, en gros

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

NSString *s = [object cents:n];

Je suis en train de concevoir une méthode qui sera de retour 01, 02, etc ... jusqu'à 99 en tant que chaîne. Je ne peux pas sembler comprendre comment retourner la mantisse comme une chaîne dans ce format. Je me sens comme si je manque une méthode pratique, mais je regardais la documentation à nouveau et ne vois rien sauter à moi.

Était-ce utile?

La solution

Mise à jour: Ceci est une réponse relativement ancienne, mais il semble que les gens trouvent encore. Je veux mettre à jour ce pour l'exactitude - je répondu à l'origine la question comme je l'ai simplement pour montrer comment on pourrait tirer des endroits décimaux spécifiques d'un double, mais je ne préconise pas cela comme une façon de représenter l'information monétaire

.

Jamais en utilisant des nombres à virgule flottante pour représenter l'information monétaire. Dès que vous commencez à jongler avec les chiffres décimaux (comme vous le feriez avec des dollars avec cents), vous présenter des erreurs possibles à virgule flottante dans votre code. Un ordinateur ne peut pas représenter toutes les valeurs décimales avec précision (1/100.0, par exemple, 1 cent, est représentée comme 0.01000000000000000020816681711721685132943093776702880859375 sur ma machine). En fonction des monnaies en fonction de son montant de base que vous envisagez de représenter, il est toujours plus correct de stocker une quantité (dans ce cas, cents).

Si vous stockez vos valeurs en dollars en cents entiers, vous ne manquerez jamais dans des erreurs à virgule flottante pour la plupart des opérations, et il est trivialement facile à convertir en cents dollars pour le formatage. Si vous avez besoin d'appliquer la taxe, par exemple, ou multiplier votre valeur cents par un double, faire pour obtenir une valeur de double, appliquez l'arrondissement de banquier pour arrondir au cent le plus proche, et reconvertir en un entier.

Il est plus compliqué que cela, si vous essayez de prendre en charge plusieurs devises différentes, mais il y a des façons de faire face à cela.

tl; dr Ne pas utiliser les nombres à virgule flottante pour représenter la monnaie et vous serez beaucoup plus heureux et plus juste. NSDecimalNumber est en mesure de précision (et avec précision) représentent des valeurs décimales, mais dès que vous passez à double / float, vous courez le risque d'introduire des erreurs à virgule flottante.


Cela peut se faire relativement facilement:

  1. Obtenir la valeur double du nombre décimal (cela fonctionnera en supposant que le nombre ne soit pas trop grand pour stocker dans un double).
  2. Dans une variable indépendante, la fonte double à un nombre entier.
  3. Multiplier les chiffres de 100 à compte pour la perte de précision (essentiellement, converti à cents) et en dollars Soustraire de l'original pour obtenir le nombre de cents.
  4. Retour dans une chaîne.

. (Ceci est une formule générale pour travailler avec tous les nombres décimaux - travailler avec NSDecimalNumber nécessite juste un peu de code de la colle pour l'obtenir au travail)

Dans la pratique, il ressemblerait à ceci (dans une catégorie de NSDecimalNumber):

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

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

Autres conseils

Ceci est une mise en œuvre plus sûre qui travaillera avec des valeurs qui ne rentrent pas dans un 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

Cette impression 01:

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

Je ne suis pas d'accord avec la méthode de Itai Ferber, parce que l'utilisation de la méthode de doubleValue de NSNumber peut entraîner la perte de précision comme 1,60 >> 1,5999999999, de sorte que votre valeur cents sera « 59 ». Au lieu de cela je coupe "dollars" / "cents" de chaîne, obtenu avec 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];
}

Version Swift, avec fonction de bonus pour obtenir juste le montant en dollars aussi.

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

J'ai une approche différente. Il fonctionne pour moi, mais je ne suis pas sûr que cela pourrait causer des problèmes? Je laisse NSDecimalNumber revenir directement la valeur en retournant une valeur entière ...

par exemple:.

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

Would cette approche soit plus cher depuis que je suis en train de convertir NSDecimalNumber deux fois?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top