You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为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

火山引擎 最新活动