Domanda

Sto usando NSDecimalNumber per archiviare un valore per la valuta. Sto cercando di scrivere un metodo chiamato "Cents" che restituisce la parte decimale del numero come NSString con uno 0 se il numero è <10. Quindi in pratica

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

NSString *s = [object cents:n];

E sto cercando di elaborare un metodo che restituirà 01, 02, ecc ... fino a 99 come stringa. Non riesco a capire come restituire la mantissa come stringa in questo formato. Mi sento come se mi mancasse un metodo di convenienza, ma ho guardato di nuovo la documentazione e non vedo nulla che mi salti fuori.

È stato utile?

Soluzione

Aggiornare: Questa è una risposta relativamente vecchia, ma sembra che la gente lo stia ancora trovando. Voglio aggiornarlo per la correttezza - Inizialmente ho risposto alla domanda come ho fatto semplicemente per dimostrare come si potevano estrarre posti decimali specifici da un double, ma non lo sostengo come un modo per rappresentare le informazioni valutarie.

Mai Utilizzare numeri a punto mobile per rappresentare le informazioni in valuta. Non appena inizi a gestire i numeri decimali (come faresti con dollari con centesimi), si introducono possibili errori di punta mobile nel tuo codice. Un computer non può rappresentare con precisione tutti i valori decimali (1/100.0, ad esempio, 1 centesimi, è rappresentato come 0.01000000000000000020816681711721685132943093776702880859375 sulla mia macchina). A seconda delle valute che prevedi di rappresentare, è sempre più corretto archiviare una quantità in termini di importo di base (in questo caso, centesimi).

Se memorizzi i tuoi valori in dollari in termini di centesimi integrali, non ti imbatterai mai in errori a punta mobile per la maggior parte delle operazioni ed è banalmente facile convertire i centesimi in dollari per la formattazione. Se è necessario applicare le tasse, ad esempio, o moltiplicare il valore dei centesimi per un double, fallo per ottenere un double valore, applicare Arrotondamento del banchiere Per arrotondare al centesimo più vicino e convertire di nuovo in un numero intero.

Diventa più complicato di quello se stai cercando di supportare più valute diverse, ma ci sono modi per affrontarlo.

tl; dr Non usare numeri a punto mobile per rappresentare la valuta e sarai molto più felice e più corretto. NSDecimalNumber è in grado di rappresentare con precisione (e precisamente) i valori decimali, ma non appena ti converti a double/float, corri il rischio di introdurre errori a virgola mobile.


Questo può essere fatto relativamente facilmente:

  1. Ottenere il double Valore del numero decimale (questo funzionerà supponendo che il numero non sia troppo grande per essere archiviato in un doppio).
  2. In una variabile separata, lancia il double a un numero intero.
  3. Moltiplica entrambi i numeri per 100 per tenere conto della perdita di precisione (essenzialmente, convertire in centesimi) e sottrarre dollari dall'originale per ottenere il numero di centesimi.
  4. Restituire in una stringa.

(Questa è una formula generale per lavorare con tutti i numeri decimali: lavorare con NSDecimalNumber Richiede solo un po 'di codice colla per farlo funzionare).

In pratica, sarebbe così (in un NSDecimalNumber categoria):

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

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

Altri suggerimenti

Questa è un'implementazione più sicura che funzionerà con valori che non si adattano a a 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

Questo stampe 01:

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

Non sono d'accordo con il metodo di Itai Ferber, perché usando doubleValue metodo di NSNumber Può causare perdita di precisione come 1.60 >> 1.599999999, quindi il valore dei centesimi sarà "59". Invece ho tagliato "dollari"/"centesimi" dalla corda, ottenuto con 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];
}

Versione rapida, con funzione bonus per ottenere anche l'importo in dollari.

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

Test:

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

Ho un approccio diverso. Funziona per me ma non sono sicuro che potrebbe causare problemi? Lascio che nsdecimalnumber restituisca il valore direttamente restituendo un valore intero ...

per esempio:

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

Questo approccio sarebbe più costoso dal momento che sto convertendo due volte NSDecimalNumber?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top