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
相关产品推荐
相关产品推荐

