在iOS中需要精确计算时,double, float类型往往会出现不可预料的问题:
在调试过程中可以看到 doule 类型 d3 在计算过程中值不是预计的9999.99。
如果在要用中计算涉及到金额等重要数据,不建议使用这种不可控结果的方式,可以采用iOS提供的另外一种支持准确精度计算的数据类型 NSDecimalNumber.
NSDecimalNumber是NSNumber的子类,比NSNumber的功能更为强大,可以指定一个数的幂,四舍五入等操作。由于NSDecimalNumber精度较高,所以会比基本数据类型费时,所以需要权衡考虑,苹果官方建议在货币以及要求精度很高的场景下使用。
所有NSDecimalNumber对象是不可变的,这意味着已经被创建后不能改变它们的值
NS_ASSUME_NONNULL_BEGIN
/*************** Exceptions ***********/
FOUNDATION_EXPORT NSString * const NSDecimalNumberExactnessException;
FOUNDATION_EXPORT NSString * const NSDecimalNumberOverflowException;
FOUNDATION_EXPORT NSString * const NSDecimalNumberUnderflowException;
FOUNDATION_EXPORT NSString * const NSDecimalNumberDivideByZeroException;
/*************** Rounding and Exception behavior ***********/
@class NSDecimalNumber;
@protocol NSDecimalNumberBehaviors
- (NSRoundingMode)roundingMode;
- (short)scale;
// The scale could return NO_SCALE for no defined scale.
- (nullable NSDecimalNumber *)exceptionDuringOperation:(SEL)operation error:(NSCalculationError)error leftOperand:(NSDecimalNumber *)leftOperand rightOperand:(nullable NSDecimalNumber *)rightOperand;
// Receiver can raise, return a new value, or return nil to ignore the exception.
@end
/*************** NSDecimalNumber: the class ***********/
@interface NSDecimalNumber : NSNumber {
@private
signed int _exponent:8;
unsigned int _length:4;
unsigned int _isNegative:1;
unsigned int _isCompact:1;
unsigned int _reserved:1;
unsigned int _hasExternalRefCount:1;
unsigned int _refs:16;
unsigned short _mantissa[0]; /* GCC */
}
- (instancetype)initWithMantissa:(unsigned long long)mantissa exponent:(short)exponent isNegative:(BOOL)flag;
- (instancetype)initWithDecimal:(NSDecimal)dcm NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithString:(nullable NSString *)numberValue;
- (instancetype)initWithString:(nullable NSString *)numberValue locale:(nullable id)locale;
- (NSString *)descriptionWithLocale:(nullable id)locale;
@property (readonly) NSDecimal decimalValue;
/*
以 -a×10ⁿ 为例:
mantissa:无符号长整型。表示a
exponent:短整形。幂级数n
flag:符号,YES:前面带负号。-a×10ⁿ
NO:前面无符号。a×10ⁿ
eg:
NSDecimalNumber*num20=[NSDecimalNumber decimalNumberWithMantissa:12 exponent:2 isNegative:YES];//-1200
NSDecimalNumber*num21=[NSDecimalNumber decimalNumberWithMantissa:12 exponent:2 isNegative:NO];//1200
NSDecimalNumber*num22=[NSDecimalNumber decimalNumberWithMantissa:12.6 exponent:2 isNegative:YES];//-1200
NSDecimalNumber*num23=[NSDecimalNumber decimalNumberWithMantissa:12.6 exponent:2 isNegative:NO];//1200
*/
+ (NSDecimalNumber *)decimalNumberWithMantissa:(unsigned long long)mantissa exponent:(short)exponent isNegative:(BOOL)flag;
+ (NSDecimalNumber *)decimalNumberWithDecimal:(NSDecimal)dcm;
//字符串值转NSDecimalNumber
+ (NSDecimalNumber *)decimalNumberWithString:(nullable NSString *)numberValue;
+ (NSDecimalNumber *)decimalNumberWithString:(nullable NSString *)numberValue locale:(nullable id)locale;
+ (NSDecimalNumber *)zero;//0
+ (NSDecimalNumber *)one;//1
+ (NSDecimalNumber *)minimumDecimalNumber;//平台所能表达的最小数(有符号)
+ (NSDecimalNumber *)maximumDecimalNumber;//平台所能表达的最大数(有符号)
+ (NSDecimalNumber *)notANumber;
//加法运算
- (NSDecimalNumber *)decimalNumberByAdding:(NSDecimalNumber *)decimalNumber;
- (NSDecimalNumber *)decimalNumberByAdding:(NSDecimalNumber *)decimalNumber withBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//减法运算
- (NSDecimalNumber *)decimalNumberBySubtracting:(NSDecimalNumber *)decimalNumber;
- (NSDecimalNumber *)decimalNumberBySubtracting:(NSDecimalNumber *)decimalNumber withBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//乘法运算
- (NSDecimalNumber *)decimalNumberByMultiplyingBy:(NSDecimalNumber *)decimalNumber;
- (NSDecimalNumber *)decimalNumberByMultiplyingBy:(NSDecimalNumber *)decimalNumber withBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//除法运算
- (NSDecimalNumber *)decimalNumberByDividingBy:(NSDecimalNumber *)decimalNumber;
- (NSDecimalNumber *)decimalNumberByDividingBy:(NSDecimalNumber *)decimalNumber withBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//power次幂计算(power表示几次幂)
- (NSDecimalNumber *)decimalNumberByRaisingToPower:(NSUInteger)power;
- (NSDecimalNumber *)decimalNumberByRaisingToPower:(NSUInteger)power withBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//乘以10的power次幂(power表示幂级)
- (NSDecimalNumber *)decimalNumberByMultiplyingByPowerOf10:(short)power;
- (NSDecimalNumber *)decimalNumberByMultiplyingByPowerOf10:(short)power withBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//指定一些有效信息,例如四舍五入的情况,小数后保留位数的情况以及数据溢出或除以零的情况等。
- (NSDecimalNumber *)decimalNumberByRoundingAccordingToBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
//比较(返回NSComparisonResult的枚举值)
- (NSComparisonResult)compare:(NSNumber *)decimalNumber;
+ (void)setDefaultBehavior:(id <NSDecimalNumberBehaviors>)behavior;
+ (id <NSDecimalNumberBehaviors>)defaultBehavior;
// One behavior per thread - The default behavior is
// rounding mode: NSRoundPlain
// scale: No defined scale (full precision)
// ignore exactnessException
// raise on overflow, underflow and divide by zero.
@property (readonly) const char *objCType NS_RETURNS_INNER_POINTER;
// return 'd' for double
@property (readonly) double doubleValue;
// return an approximate double value
@end
/************************A class for defining common behaviors ****************************************/
//用于指定一些有效信息,例如四舍五入的情况,小数后保留位数的情况以及数据溢出或除以零的情况等。
//当exact,overflow,underflow,divideByZero为NO时,在出现上溢,下溢,除数为零时,返回NaN(可能是notANumber的意思),程序不会崩溃。
//当值为YES时,出现对应的情况程序直接崩掉。
@interface NSDecimalNumberHandler : NSObject <NSDecimalNumberBehaviors, NSCoding> {
@private
signed int _scale:16;
unsigned _roundingMode:3;
unsigned _raiseOnExactness:1;
unsigned _raiseOnOverflow:1;
unsigned _raiseOnUnderflow:1;
unsigned _raiseOnDivideByZero:1;
unsigned _unused:9;
void *_reserved2;
void *_reserved;
}
+ (NSDecimalNumberHandler *)defaultDecimalNumberHandler;
// rounding mode: NSRoundPlain
// scale: No defined scale (full precision)
// ignore exactnessException (return nil)
// raise on overflow, underflow and divide by zero.
- (instancetype)initWithRoundingMode:(NSRoundingMode)roundingMode scale:(short)scale raiseOnExactness:(BOOL)exact raiseOnOverflow:(BOOL)overflow raiseOnUnderflow:(BOOL)underflow raiseOnDivideByZero:(BOOL)divideByZero NS_DESIGNATED_INITIALIZER;
+ (instancetype)decimalNumberHandlerWithRoundingMode:(NSRoundingMode)roundingMode scale:(short)scale raiseOnExactness:(BOOL)exact raiseOnOverflow:(BOOL)overflow raiseOnUnderflow:(BOOL)underflow raiseOnDivideByZero:(BOOL)divideByZero;
@end
/**************************************Extensions to other classes*********************************/
@interface NSNumber (NSDecimalNumberExtensions)
@property (readonly) NSDecimal decimalValue;
// Could be silently inexact for float and double.
@end
@interface NSScanner (NSDecimalNumberScanning)
- (BOOL)scanDecimal:(nullable NSDecimal *)dcm;
@end
NS_ASSUME_NONNULL_END
//四舍五入截取指定小数位的值(price:目标数据 position:有效小数位), 返回截取后数据
/*
typedef NS_ENUM(NSUInteger, NSRoundingMode) {
NSRoundPlain, //用于四舍五入
NSRoundDown, //只舍不入
NSRoundUp, //不舍只入
NSRoundBankers //。。。取最精确的,当遇到5时,舍和入是一样的,所以可以有两个值。(待指教)
};
Original Value |
NSRoundPlain |
NSRoundDown |
NSRoundUp |
NSRoundBankers |
---|---|---|---|---|
1.24 |
1.2 |
1.2 |
1.3 |
1.2 |
1.26 |
1.3 |
1.2 |
1.3 |
1.3 |
1.25 |
1.3 |
1.2 |
1.3 |
1.2 |
1.35 |
1.4 |
1.3 |
1.4 |
1.4 |
–1.35 |
–1.4 |
–1.4 |
–1.3 |
–1.4 |
*/
+(NSString *)notRounding:(float)price afterPoint:(NSInteger)position
{
NSDecimalNumberHandler* roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:position raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO];
NSDecimalNumber *ouncesDecimal;
NSDecimalNumber *roundedOunces;
ouncesDecimal = [[NSDecimalNumber alloc] initWithFloat:price];
roundedOunces = [ouncesDecimal decimalNumberByRoundingAccordingToBehavior:roundingBehavior];
return [NSString stringWithFormat:@"%@",roundedOunces];
}