7

NSDecimalNumberを使用して通貨の値を格納しています。数値が10未満の場合、数値の小数部分を先行ゼロのNSStringとして返す「cents」というメソッドを作成しようとしています。

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

NSString *s = [object cents:n];

そして、01、02などを文字列として99まで返すメソッドを作成しようとしています。この形式で仮数を文字列として返す方法がわからないようです。便利な方法が欠けているような気がしますが、ドキュメントをもう一度見てみると、何も飛び出していません。

4

5 に答える 5

11

更新:これは比較的古い答えですが、人々はまだそれを見つけているようです。正確を期すためにこれを更新したい—もともとは、から特定の小数点以下の桁数を引き出す方法を示すために行ったのと同じように質問に答えましたが、double通貨情報を表す方法としてこれを推奨していません。

通貨情報を表すために浮動小数点数を使用しないでください。(セント付きのドルの場合と同様に)10進数の処理を開始するとすぐに、コードに浮動小数点エラーが発生する可能性があります。コンピューターはすべての10進値を正確に表すことはできません(1/100.0たとえば、1セントは0.01000000000000000020816681711721685132943093776702880859375私のマシンのように表されます)。表示する予定の通貨によっては、基本金額(この場合はセント)で数量を保存する方が常に正確です。

ドルの値を整数セントで保存すると、ほとんどの操作で浮動小数点エラーが発生することはなく、フォーマットのためにセントをドルに変換するのは簡単です。たとえば、税金を適用する必要がある場合、またはセントの値にaを掛ける必要がある場合は、それdoubleを実行して値を取得し、銀行の丸めdoubleを適用して最も近いセントに丸め、整数に変換し直します。

複数の異なる通貨をサポートしようとしている場合よりも複雑になりますが、それに対処する方法はいくつかあります。

tl; dr通貨を表すために浮動小数点数を使用しないでください。そうすれば、はるかに幸せになり、より正確になります。NSDecimalNumberは10進値を正確に(そして正確に)表すことができますが、double/floatに変換するとすぐに、浮動小数点エラーが発生するリスクがあります。


これは比較的簡単に行うことができます。

  1. double10進数の値を取得します(これは、数値が大きすぎてdoubleに格納できない場合に機能します)。
  2. double別の変数で、を整数にキャストします。
  3. 精度の低下を考慮して両方の数値に100を掛け(基本的にはセントに変換)、元の数値からドルを引いてセント数を求めます。
  4. 文字列で返します。

(これは、すべての10進数を操作するための一般式ですNSDecimalNumber。操作するには、わずかなグルーコードが必要です)。

実際には、次のようになります(NSDecimalNumberカテゴリ内):

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

    return [NSString stringWithFormat:@"%02u", cents];
}
于 2011-03-18T18:44:51.217 に答える
11

doubleこれは、 :に適合しない値で機能するより安全な実装です。

@implementation NSDecimalNumber(centsStringAddition)

-(NSString *)centsString;
{{
    NSDecimal値=[自己decimalValue];

    NSDecimalドル;
    NSDecimalRound(&dollars、&value、0、NSRoundPlain);

    NSDecimalセント;
    NSDecimalSubtract(&cents、&value、&dollars、NSRoundPlain);

    double dv = [[[NSDecimalNumber decimalNumberWithDecimal:cents] decimalNumberByMultiplyingByPowerOf10:2] doubleValue];
    return [NSString stringWithFormat:@ "%02d"、(int)dv];
}

@終わり

これは印刷します01

    NSDecimalNumber * dn = [[NSDecimalNumber alloc] initWithString:@ "123456789012345678901234567890.0108"];
    NSLog(@ "%@"、[dn centsString]);
于 2011-08-12T08:13:19.007 に答える
2

doubleValueの方法を使用するとNSNumber、1.60 >> 1.5999999999のように精度が失われる可能性があるため、Itai Ferberの方法に同意しません。したがって、セント値は「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];
}
于 2013-03-02T11:17:13.980 に答える
2

ドル額だけをゲットできるボーナス機能付きのスウィフトバージョン。

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")
    }
}
于 2016-03-07T21:15:26.957 に答える
1

私には別のアプローチがあります。それは私にとってはうまくいきますが、それが問題を引き起こす可能性があるかどうかはわかりませんか?NSDecimalNumberに整数値を返すことにより、値を直接返すようにします。

例えば:

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

NSDecimalNumberを2回変換しているので、このアプローチはより高価になりますか?

于 2011-08-12T07:21:18.850 に答える