如何为NSDecimalNumber实现HalfDown(平局时向下舍入)舍入方式?
实现NSDecimalNumber的HalfDown舍入模式
这个问题确实戳中了NSDecimalNumber的一个小痛点——系统自带的舍入模式里刚好缺了HalfDown(平局时向下舍入,和.plain的平局向上完全相反)。不过别担心,我们可以通过扩展NSDecimalNumber来实现这个需求,而且全程基于精确的十进制计算,完全适配金额这类对精度要求极高的场景。
实现思路
HalfDown的核心规则是:
- 当舍去部分小于0.5×10^(-精度值)时,向下舍入
- 当舍去部分大于0.5×10^(-精度值)时,向上舍入
- 当舍去部分正好等于0.5×10^(-精度值)时,强制向下舍入
基于这个规则,我们可以先通过系统的.plain模式得到初步结果,再判断是否属于“平局”情况:如果是,就改用.down模式重新计算;如果不是,直接使用.plain的结果即可。
代码实现
我们给NSDecimalNumber写一个扩展,新增支持HalfDown的舍入方法:
import Foundation extension NSDecimalNumber { // 自定义包含HalfDown的舍入模式枚举 enum CustomRoundingMode: UInt { case plain, down, up, bankers, halfDown } /// 支持HalfDown模式的舍入方法 /// - Parameters: /// - scale: 保留的小数位数 /// - mode: 自定义舍入模式 /// - Returns: 舍入后的NSDecimalNumber func rounded(toScale scale: Int, roundingMode mode: CustomRoundingMode) -> NSDecimalNumber { // 系统已支持的模式直接调用原生方法 let systemMode: RoundingMode? switch mode { case .plain: systemMode = .plain case .down: systemMode = .down case .up: systemMode = .up case .bankers: systemMode = .bankers case .halfDown: systemMode = nil } if let mode = systemMode { let handler = NSDecimalNumberHandler( roundingMode: mode, scale: scale, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false ) return self.rounded(accordingToBehavior: handler) } // 处理HalfDown模式 // 1. 先通过plain模式得到初步结果 let plainHandler = NSDecimalNumberHandler( roundingMode: .plain, scale: scale, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false ) let plainRounded = self.rounded(accordingToBehavior: plainHandler) // 2. 计算当前精度下的"半单位"(比如scale=0时是0.5,scale=2时是0.005) let halfUnit = NSDecimalNumber( mantissa: 5, exponent: -scale - 1, isNegative: false ) // 3. 判断是否是平局:原数与plain结果的差值绝对值是否等于半单位 let difference = self.subtracting(plainRounded).abs() if difference.compare(halfUnit) == .orderedSame { // 平局情况,改用down模式舍入 let downHandler = NSDecimalNumberHandler( roundingMode: .down, scale: scale, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false ) return self.rounded(accordingToBehavior: downHandler) } else { // 非平局,直接使用plain的结果 return plainRounded } } }
验证示例
我们用几个典型案例测试一下:
NSDecimalNumber(string: "1.5").rounded(toScale: 0, roundingMode: .halfDown)→ 结果是1(符合平局向下舍入)NSDecimalNumber(string: "1.6").rounded(toScale: 0, roundingMode: .halfDown)→ 结果是2(舍去部分大于0.5,向上舍入)NSDecimalNumber(string: "1.25").rounded(toScale: 1, roundingMode: .halfDown)→ 结果是1.2(平局向下舍入)NSDecimalNumber(string: "-1.5").rounded(toScale: 0, roundingMode: .halfDown)→ 结果是-2(负数向下舍入为更小的数,符合HalfDown规则)
这个实现完全基于NSDecimalNumber的精确计算,不会出现float/double的精度丢失问题,非常适合金额计算这类场景。
内容的提问来源于stack exchange,提问作者Eddie




