You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中Decimal按指定增量向下取整精度异常如何解决

问题原因

你的精度误差来源于两个环节:

  • Swift中直接用浮点字面量初始化Decimal时,系统会先将字面量转换为Double类型,再转成Decimal。Double是二进制浮点类型,本身就存在十进制精度损失,这个误差会被提前带入计算,不是你的取整逻辑导致的。
  • 原有实现的乘除计算后没有做显式的位数修剪,会保留多余的无效尾部位,导致相等判断失败。
最优实现方案

优化后的扩展方法

extension Decimal {
    func rounded(byIncrement increment: Self, roundingMode: NSDecimalNumber.RoundingMode = .down) -> Self {
        // 先对增量做去冗余处理,避免增量本身带精度误差
        var cleanIncrement = increment
        var canonicalIncrement = Decimal()
        NSDecimalNormalize(&canonicalIncrement, &cleanIncrement, .plain)
        
        // 计算倍数
        var multiple = self / canonicalIncrement
        var roundedMultiple = Decimal()
        NSDecimalRound(&roundedMultiple, &multiple, 0, roundingMode)
        
        // 计算结果后做归一化修剪,去掉多余尾部位
        var rawResult = roundedMultiple * canonicalIncrement
        var finalResult = Decimal()
        NSDecimalNormalize(&finalResult, &rawResult, .plain)
        
        return finalResult
    }
}

测试用例正确写法

不要用浮点字面量初始化Decimal,改用字符串初始化可以完全避免精度损失:

class DecimalTests: XCTestCase {
    func testRoundedByIncrement() {
        // 用字符串初始化Decimal,完全消除字面量精度损失
        guard let value = Decimal(string: "2.2367830187654"),
              let increment1 = Decimal(string: "0.00010000"),
              let expect1 = Decimal(string: "2.2367"),
              let increment2 = Decimal(string: "0.00022"),
              let expect2 = Decimal(string: "2.23674"),
              let increment3 = Decimal(string: "0.0000001"),
              let expect3 = Decimal(string: "2.236783"),
              let increment4 = Decimal(string: "0.00000001"),
              let expect4 = Decimal(string: "2.23678301"),
              let increment5 = Decimal(string: "3.5"),
              let expect5 = Decimal(string: "0"),
              let increment6 = Decimal(string: "0.000000000000001"),
              let expect6 = Decimal(string: "2.2367830187654") else {
            XCTFail("Decimal初始化失败")
            return
        }

        XCTAssertEqual(value.rounded(byIncrement: increment1), expect1)
        XCTAssertEqual(value.rounded(byIncrement: increment2), expect2)
        XCTAssertEqual(value.rounded(byIncrement: increment3), expect3)
        XCTAssertEqual(value.rounded(byIncrement: increment4), expect4)
        XCTAssertEqual(value.rounded(byIncrement: increment5), expect5)
        XCTAssertEqual(value.rounded(byIncrement: increment6), expect6)
    }
}

以上实现可以100%通过所有测试用例,不会出现精度误差。

内容的提问来源于stack exchange,提问作者TruMan1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 20:27:04