项目里经常有这么一种需求,把小数四舍五入到指定位数,查阅文档,我们发现系统提供了NSDecimalNumber
类方便我们实现需求。NSDecimalNumber
是系统提供的一个专门处理十进位制数的类:1
NSDecimalNumber, an immutable subclass of NSNumber, provides an object-oriented wrapper for doing base-10 arithmetic. An instance can represent any number that can be expressed as mantissa x 10^exponent where mantissa is a decimal integer up to 38 digits long, and exponent is an integer from –128 through 127.
NSDecimalNumber
提供了处理舍入问题的方法:1
2
3Rounds the receiver off in the way specified by behavior and returns the result, a newly createdNSDecimalNumber object.
- (NSDecimalNumber *)decimalNumberByRoundingAccordingToBehavior:(nullable id <NSDecimalNumberBehaviors>)behavior;
该方法需要调用者提供一个遵循NSDecimalNumerBehaviors
协议的执行者来处理舍入问题。这里我们无需自定义一个类,只需使用系统提供的NSDecimalNumberHandler
类来处理。该类已遵循NSDecimalNumerBehaviors
协议,且提供了一些方法方便我们实现:1
NSDecimalNumberHandler is a class that adopts the NSDecimalNumberBehaviors protocol. This class allows you to set the way an NSDecimalNumber object rounds off and handles errors, without having to create a custom class.
NSDecimalNumberHandler提供的舍入处理方法:1
2
3Returns an NSDecimalNumberHandler object with customized behavior.
+ (instancetype)decimalNumberHandlerWithRoundingMode:(NSRoundingMode)roundingMode scale:(short)scale raiseOnExactness:(BOOL)exact raiseOnOverflow:(BOOL)overflow raiseOnUnderflow:(BOOL)underflow raiseOnDivideByZero:(BOOL)divideByZero;
该方法需要我们设置六个参数,第一个参数为舍入模式(roundingMode),第二个参数为保留位数(scale,以小数点为中心,正数为小数位,负数为整数位),第三个参数为数值精度异常捕获(raiseOnExactness),第四个参数为数值上溢异常捕获(raiseOnOverflow),第五个参数为数值下溢异常捕获(raiseOnUnderflow),最后一个参数为数值除数为零异常捕获(raiseOnDivideByZero):1
2
3
4
5
6
7
8
9
10
11roundingMode:The rounding mode to use. There are four possible values: NSRoundUp,NSRoundDown, NSRoundPlain, and NSRoundBankers.
scale:The number of digits a rounded value should have after its decimal point.
raiseOnExactness:If YES, in the event of an exactness error the handler will raise an exception, otherwise it will ignore the error and return control to the calling method.
raiseOnOverflow:If YES, in the event of an overflow error the handler will raise an exception, otherwise it will ignore the error and return control to the calling method.
raiseOnUnderflow:If YES, in the event of an underflow error the handler will raise an exception, otherwise it will ignore the error and return control to the calling method.
raiseOnDivideByZero:If YES, in the event of a divide by zero error the handler will raise an exception, otherwise it will ignore the error and return control to the calling method.
参数可根据实际需求设定,需要注意的是舍入模式(roundingMode)分别有四种,在这里我们使用NSRoundPlain
模式:1
2
3
4
5
6
7
8
9
10
11
12
13
14// Rounding policies :
// Original
// value 1.2 1.21 1.25 1.35 1.27
// Plain 1.2 1.2 1.3 1.4 1.3
// Down 1.2 1.2 1.2 1.3 1.2
// Up 1.2 1.3 1.3 1.4 1.3
// Bankers 1.2 1.2 1.2 1.4 1.3
typedef NS_ENUM(NSUInteger, NSRoundingMode) {
NSRoundPlain, // Round up on a tie
NSRoundDown, // Always down == truncate
NSRoundUp, // Always up
NSRoundBankers // on a tie round so last digit is even
};
基本要素齐备后,可以进行方法接口设计。在这里我们采用NSDecimalNumber
分类方法的形式实现。为NSDecimalNumber
提供舍入方法,根据舍入模式舍入到指定位数:1
2
3
4
5- (NSDecimalNumber *)roundToScale:(short)scale mode:(NSRoundingMode)roundingMode
{
NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:roundingMode scale:scale raiseOnExactness:NO raiseOnOverflow:YES raiseOnUnderflow:YES raiseOnDivideByZero:YES];
return [self decimalNumberByRoundingAccordingToBehavior:handler];
}
四舍五入到指定位数:1
2
3
4- (NSDecimalNumber *)roundToScale:(short)scale
{
return [self roundToScale:scale mode:NSRoundPlain];
}
为float和double类型数值添加创建方法。根据float数值、舍入位数和舍入模式, 创建decimalNumber:1
2
3
4+ (NSDecimalNumber *)decimalNumberWithFloat:(float)value roundingScale:(short)scale roundingMode:(NSRoundingMode)mode
{
return [[[NSDecimalNumber alloc] initWithFloat:value] roundToScale:scale mode:mode];
}
根据float数值和四舍五入位数, 创建decimalNumber:1
2
3
4+ (NSDecimalNumber *)decimalNumberWithFloat:(float)value roundingScale:(short)scale
{
return [[[NSDecimalNumber alloc] initWithFloat:value] roundToScale:scale];
}
根据double数值、舍入位数和舍入模式, 创建decimalNumber:1
2
3
4+ (NSDecimalNumber *)decimalNumberWithDouble:(double)value roundingScale:(short)scale roundingMode:(NSRoundingMode)mode
{
return [[[NSDecimalNumber alloc] initWithDouble:value] roundToScale:scale mode:mode];
}
根据double数值和四舍五入位数, 创建decimalNumber:1
2
3
4+ (NSDecimalNumber *)decimalNumberWithDouble:(double)value roundingScale:(short)scale
{
return [[[NSDecimalNumber alloc] initWithDouble:value] roundToScale:scale];
}
至此,我们在需要四舍五入数值时调用即可:1
2// 四舍五入至小数两位
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithFloat:2.567f roundingScale:2];
以上方法适用于一般四舍五入需求,然而有时候我们可能面对这样的需求,任意数值四舍五入至指定小数位后且保留并显示至指定小数位,不足的补0显示(例,1.445->1.45; 1.4->1.40)。而以上方法默认输出数值是舍去尾数0的。对此,我们有两种方法实现,第一种是四舍五入后返回decimalNumber的floatValue或doubleValue后再处理:1
2
3
4
5
6// 四舍五入并显示至小数后两位, 不足0补齐
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithFloat:2.5f roundingScale:2];
NSLog(@"%.2f", [decimalNumber floatValue]);
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithDouble:2.5 roundingScale:2];
NSLog(@"%.2f", [decimalNumber doubleValue]);
该方法虽然简单,但是会破坏数值精度。若想不破话数值精度,则可以通过NSNumberFomatter
实现:1
2
3
4
5
6// 四舍五入并显示至小数后两位, 不足0补齐
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithFloat:value roundingScale:2];
NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
[numberFormatter setMaximumFractionDigits:2];
[numberFormatter setMinimumFractionDigits:2];
NSLog(@"%@", [numberFormatter stringFromNumber:decimalNumber]);
同时,我们可以通过NSString
分类方法进行扩展,方便实现:
根据number和保留显示小数位数,获取对应的字符串:1
2
3
4
5
6
7+ (NSString *)stringFromNumber:(NSNumber *)number fractionDigits:(NSUInteger)fractionDigits
{
NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
[numberFormatter setMaximumFractionDigits:fractionDigits];
[numberFormatter setMinimumFractionDigits:fractionDigits];
return [numberFormatter stringFromNumber:number];
}
根据float数值、舍入位数、舍入模式和保留显示小数位数,获取对应的字符串:1
2
3
4
5+ (NSString *)stringFromFloat:(float)value roundingScale:(short)scale roundingMode:(NSRoundingMode)mode fractionDigits:(NSUInteger)fractionDigits
{
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithFloat:value roundingScale:scale roundingMode:mode];
return [NSString stringFromNumber:decimalNumber fractionDigits:fractionDigits];
}
根据float数值、舍入位数、舍入模式和是否补齐显示小数位数(不足补0,补齐位数与四舍五入位数一致),获取对应的字符串:1
2
3
4
5
6
7
8+ (NSString *)stringFromFloat:(float)value roundingScale:(short)scale roundingMode:(NSRoundingMode)mode fractionDigitsPadded:(BOOL)isPadded
{
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithFloat:value roundingScale:scale roundingMode:mode];
if (!isPadded) return [NSString stringWithFormat:@"%@", decimalNumber];
return [NSString stringFromNumber:decimalNumber fractionDigits:scale];
}
根据float数值、四舍五入位数和是否补齐显示小数位数(不足补0,补齐位数与四舍五入位数一致),获取对应的字符串:1
2
3
4+ (NSString *)stringFromFloat:(float)value roundingScale:(short)scale fractionDigitsPadded:(BOOL)isPadded
{
return [NSString stringFromFloat:value roundingScale:scale roundingMode:NSRoundPlain fractionDigitsPadded:isPadded];
}
根据float数值和四舍五入位数,获取对应的字符串:1
2
3
4+ (NSString *)stringFromFloat:(float)value roundingScale:(short)scale
{
return [NSString stringFromFloat:value roundingScale:scale roundingMode:NSRoundPlain fractionDigitsPadded:NO];
}
double数值的实现与float类似。在需要的位置如下调用即可:1
2
3
4
5
6// 四舍五入并显示至小数后两位, 不足0补齐
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithFloat:2.4 roundingScale:2];
NSLog(@"%@", [NSString stringFromNumber:decimalNumber fractionDigits:2]);
// 四舍五入并显示至小数后两位, 不足0补齐, 补齐位数与四舍五入位数一致
NSLog(@"%@", [NSString stringFromFloat:2.4 roundingScale:2 fractionDigitsPadded:YES]);
附,示例代码:https://github.com/ColinHwang/iOS-RoundNumber
参考资料:
[1]iOS-Denzel.关于OC中的小数精确计算—NSDecimalNumber[EB/OL].http://www.cnblogs.com/denz/p/5330771.html ,2016-3-28
[2]Apple Inc.NSDecimalNumber Classs Reference[EB/OL].https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDecimalNumber_Class/index.html ,2016-6-22.
[3]Apple Inc.NSNumberFormatter Classs Reference[EB/OL].https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNumberFormatter_Class/index.html ,2016-6-22.