Frage

Ich verwende NSDecimalNumber, um einen Wert für Währung zu speichern. Ich versuche, eine Methode namens "Cent" zu schreiben, die den Dezimalanteil der Zahl als NSString mit einer führenden 0 zurückgibt, wenn die Zahl <10 ist. Im Grunde genommen also

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

NSString *s = [object cents:n];

Und ich versuche eine Methode herzustellen, die 01, 02 usw. bis zu 99 als Zeichenfolge zurückgibt. Ich kann nicht herausfinden, wie ich die Mantissa als Zeichenfolge in diesem Format zurückgeben kann. Ich habe das Gefühl, dass mir eine Komfortmethode fehlt, aber ich habe mir die Dokumentation noch einmal angesehen und sehe nicht, wie etwas auf mich springt.

War es hilfreich?

Lösung

Aktualisieren: Dies ist eine relativ alte Antwort, aber es sieht so aus, als würden die Leute sie immer noch finden. Ich möchte dies für Korrektheit aktualisieren - ich habe die Frage ursprünglich beantwortet, wie ich es einfach getan habe, um zu demonstrieren, wie man bestimmte Dezimalstellen von a herausholen kann double, Aber ich befürworte dies nicht, um Währungsinformationen darzustellen.

Niemals Verwenden Sie Floating-Punkt-Nummern, um Währungsinformationen darzustellen. Sobald Sie mit Dezimalzahlen zu tun haben (wie Sie es mit Dollar mit Cent tun würden), führen Sie mögliche schwimmende Punktfehler in Ihren Code ein. Ein Computer kann nicht alle Dezimalwerte genau darstellen (1/100.0, Zum Beispiel ist 1 Cent als dargestellt als 0.01000000000000000020816681711721685132943093776702880859375 auf meiner Maschine). Je nachdem, welche Währungen Sie vertreten möchten, ist es immer korrekter, eine Menge in Bezug auf den Grundbetrag (in diesem Fall Cent) zu speichern.

Wenn Sie Ihre Dollar-Werte in Bezug auf integrale Cent speichern, werden Sie für die meisten Operationen nie auf Floating-Punkt-Fehler stoßen, und es ist trivial einfach, Cent in Dollar für die Formatierung umzuwandeln. Wenn Sie beispielsweise Steuern anwenden oder Ihren Cent -Wert mit a multiplizieren müssen double, Tun Sie das, um eine zu bekommen double Wert, bewerben Sie sich Bankerrundung zum nächsten Cent runden und in eine Ganzzahl zurückkehren.

Es wird komplizierter als das, wenn Sie versuchen, mehrere verschiedene Währungen zu unterstützen, aber es gibt Möglichkeiten, damit umzugehen.

tl; dr Verwenden Sie keine Floating-Punkt-Zahlen, um die Währung darzustellen, und Sie werden viel glücklicher und korrekter sein. NSDecimalNumber ist in der Lage, genau (und genau) Dezimalwerte darzustellen, aber sobald Sie sich um konvertieren double/float, Sie führen das Risiko ein, Floating-Punkt-Fehler einzuführen.


Dies kann relativ leicht erfolgen:

  1. Bekommen das double Wert der Dezimalzahl (dies funktioniert unter der Annahme, dass die Zahl nicht zu groß ist, um im Doppel zu speichern).
  2. In einer separaten Variablen die wirken double zu einer Ganzzahl.
  3. Multiplizieren Sie beide Zahlen mit 100, um den Präzisionsverlust zu berücksichtigen (im Wesentlichen in Cent umzuwandeln) und subtrahieren Sie Dollar vom Original, um die Anzahl der Cent zu erhalten.
  4. In einer Zeichenfolge zurückkehren.

(Dies ist eine allgemeine Formel für die Arbeit mit allen Dezimalzahlen - Arbeiten mit NSDecimalNumber Benötigt nur ein bisschen Klebercode, um ihn zum Laufen zu bringen).

In der Praxis würde es so aussehen (in einem NSDecimalNumber Kategorie):

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

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

Andere Tipps

Dies ist eine sicherere Implementierung, die mit Werten funktioniert, die nicht in a passen 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

Dies druckt 01:

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

Ich stimme der Methode von Itai Ferber nicht zu doubleValue Methode von NSNumber Kann eine Präzision wie 1.60 >> 1.5999999999 verursachen, sodass Ihr Cent -Wert "59" beträgt. Stattdessen schneide ich "Dollar"/"Cent" aus String, erhalten mit 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];
}

Swift -Version, mit Bonusfunktion, um auch nur den Dollarbetrag zu erhalten.

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

Ich habe einen anderen Ansatz. Es funktioniert bei mir, aber ich bin mir nicht sicher, ob es Probleme verursachen könnte? Ich lasse nsdecimalnumber den Wert direkt zurückgeben, indem ich einen Ganzzahlwert zurücksende ...

z.B:

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

Wäre dieser Ansatz teurer, da ich die NSDecimalNumber zweimal umwandle?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top