nsdecimalnumberの分数部分
-
27-10-2019 - |
質問
通貨の値を保存するためにnsdecimalnumberを使用しています。数値が10の場合、先頭の0を持つnsstringとして数字の小数部分を返す「セント」というメソッドを書き込もうとしています。
NSDecimalNumber *n = [[NSDecimalNumber alloc] initWithString:@"1234.55"];
NSString *s = [object cents:n];
そして、私は01、02などを返す方法を作成しようとしています...最大99文字列として。この形式の文字列としてマンティッサを返す方法を理解できないようです。便利な方法が欠けているように感じますが、ドキュメントをもう一度見て、何も飛び出していません。
解決
アップデート: これは比較的古い答えですが、人々はまだそれを見つけているようです。私はこれを正しさのために更新したいと思います - 私はもともと私は単に特定の小数点をどのように引き出すことができるかを示すために私がしたように、私はもともと質問に答えました double
, 、しかし、私はこれを通貨情報を表す方法として提唱していません。
一度もない 浮動小数点数を使用して、通貨情報を表します。小数の数字を処理し始めるとすぐに(セント付きのドルを使用するように)、コードに可能な浮動小数点エラーを導入します。コンピューターはすべての小数値を正確に表すことはできません(1/100.0
, 、たとえば、1セントは 0.01000000000000000020816681711721685132943093776702880859375
私のマシンで)。代表する予定の通貨に応じて、基本額(この場合はセント)に関して数量を保存する方が常に正しいです。
不可欠なセントの観点からドルの価値を保存すると、ほとんどの操作で浮動小数点エラーに遭遇することはなく、フォーマットのためにセントをドルに変換するのは簡単です。たとえば、税金を適用する必要がある場合、またはセント値に次のように乗算する必要がある場合 double
, 、それを行います double
値、適用してください 銀行家の丸め 最も近いセントに丸くなり、整数に戻ります。
複数の異なる通貨をサポートしようとしている場合よりも複雑になりますが、それに対処する方法があります。
tl; dr 通貨を表すために浮動小数点数を使用しないでください。そうすれば、あなたはもっと幸せになり、より正確になります。 NSDecimalNumber
小数値を正確に(そして正確に)表すことができますが、変換するとすぐに double
/float
, 、フローティングポイントエラーを導入するリスクを実行します。
これは比較的簡単に行うことができます:
- 取得します
double
小数の値の値(これは、ダブルに保存するには数値が大きすぎないと仮定して機能します)。 - 別の変数で、キャストします
double
整数に。 - 両方の数値に100を掛けて、精度の損失(基本的に、セントに変換)を考慮し、オリジナルからドルを減算してセント数を取得します。
- 文字列に戻ります。
(これは、すべての小数点以下で作業するための一般的な式です。 NSDecimalNumber
うまく機能させるには、少しの接着剤コードが必要です)。
実際には、それは次のようになります( NSDecimalNumber
カテゴリー):
- (NSString *)cents {
double value = [self doubleValue];
unsigned dollars = (unsigned)value;
unsigned cents = (value * 100) - (dollars * 100);
return [NSString stringWithFormat:@"%02u", cents];
}
他のヒント
これは、に適合しない価値で機能するより安全な実装です double
:
@implementation NSDecimalNumber (centsStringAddition) - (NSString*) centsString; { NSDecimal value = [self decimalValue]; NSDecimal dollars; NSDecimalRound(&dollars, &value, 0, NSRoundPlain); NSDecimal cents; NSDecimalSubtract(¢s, &value, &dollars, NSRoundPlain); double dv = [[[NSDecimalNumber decimalNumberWithDecimal:cents] decimalNumberByMultiplyingByPowerOf10:2] doubleValue]; return [NSString stringWithFormat:@"%02d", (int)dv]; } @end
これはプリントです 01
:
NSDecimalNumber* dn = [[NSDecimalNumber alloc] initWithString:@"123456789012345678901234567890.0108"]; NSLog(@"%@", [dn centsString]);
私はItai Ferberの方法に同意しません。 doubleValue
の方法 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];
}
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"
}
}
テスト:
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")
}
}
別のアプローチがあります。それは私のために働きますが、それが問題を引き起こすかどうかはわかりませんか?整数値を返すことにより、nsdecimalnumberに値を直接返してもらいました...
例えば:
// n is the NSDecimalNumber from above
NSInteger value = [n integerValue];
NSInteger cents = ([n doubleValue] *100) - (value *100);
NSDecimalNumberを2回変換しているので、このアプローチはより高価になりますか?