给定数字时,将小数四舍五入到最接近的增量

Tru*_*an1 1 decimal rounding foundation swift

我想将小数四舍五入到另一个数字的最接近的增量。例如,给定值2.23678301和增量0.0001,我想将其舍入为2.2367。有时增量可能类似于0.00022,在这种情况下,该值将向下舍入为2.23674

我尝试这样做,但有时结果不正确并且测试未通过:

extension Decimal {
    func rounded(byIncrement increment: Self) -> Self {
        var multipleOfValue = self / increment
        var roundedMultipleOfValue = Decimal()
        NSDecimalRound(&roundedMultipleOfValue, &multipleOfValue, 0, .down)
        return roundedMultipleOfValue * increment
    }
}

/// Tests

class DecimalTests: XCTestCase {
    func testRoundedByIncrement() {
        // Given
        let value: Decimal = 2.2367830187654

        // Then
        XCTAssertEqual(value.rounded(byIncrement: 0.00010000), 2.2367)
        XCTAssertEqual(value.rounded(byIncrement: 0.00022), 2.23674)
        XCTAssertEqual(value.rounded(byIncrement: 0.0000001), 2.236783)
        XCTAssertEqual(value.rounded(byIncrement: 0.00000001), 2.23678301) // XCTAssertEqual failed: ("2.23678301") is not equal to ("2.236783009999999744")
        XCTAssertEqual(value.rounded(byIncrement: 3.5), 0)
        XCTAssertEqual(value.rounded(byIncrement: 0.000000000000001), 2.2367830187654) // XCTAssertEqual failed: ("2.2367830187653998323726489726140416") is not equal to ("2.236783018765400576")
    }
}
Run Code Online (Sandbox Code Playgroud)

我不确定为什么十进制计算会组成从未存在过的数字,就像最后一个断言一样。有没有更干净或更准确的方法来做到这一点?

Rob*_*ier 5

你的代码没问题。您只是错误地调用了它。这行代码并不符合您的想法:

let value: Decimal = 2.2367830187654
Run Code Online (Sandbox Code Playgroud)

这相当于:

let value = Decimal(double: Double(2.2367830187654))
Run Code Online (Sandbox Code Playgroud)

该值首先转换为 Double,二进制舍入为 2.236783018765400576。然后将该值转换为十进制。

您需要在任何需要数字字符串中的 Decimal 的地方使用字符串初始值设定项:

let value = Decimal(string: "2.2367830187654")!

XCTAssertEqual(value.rounded(byIncrement: Decimal(string: "0.00000001")!), Decimal(string: "2.23678301")!)
Run Code Online (Sandbox Code Playgroud)

ETC。

或者您可以使用基于整数的初始值设定项:

let value = Decimal(sign: .plus, exponent: -13, significand: 22367830187654)
Run Code Online (Sandbox Code Playgroud)

在 iOS 15 中,有一些新的初始化程序不返回可选值(init(_:format:lenient:)例如),但您仍然需要传递字符串,而不是浮点文字。

您也可以这样做,尽管这可能会让读者感到困惑,并且如果人们去掉引号,可能会导致错误:

extension Decimal: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
        self.init(string: value)!
    }
}

let value: Decimal = "2.2367830187654"

XCTAssertEqual(value.rounded(byIncrement: "0.00000001"), "2.23678301")
Run Code Online (Sandbox Code Playgroud)

对于测试代码来说,这可能很好,但在生产代码中使用它时我会非常小心。

  • 我做了一些测试,得出了与您相同的结论,然后回来发布答案,却发现您打败了我。(已投票。) (2认同)