给定一个数字,将小数点四舍五入到最接近的增量
Round decimal to nearest increment given a number
我想将小数点向下舍入到另一个数字的最接近增量。例如,给定 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")
}
}
我不确定为什么小数计算会组成从未存在过的数字,就像上一个断言一样。有没有更简洁或更准确的方法来做到这一点?
你的代码没问题。你只是错误地称呼它。此行不符合您的想法:
let value: Decimal = 2.2367830187654
这相当于:
let value = Decimal(double: Double(2.2367830187654))
该值首先转换为 Double,二进制四舍五入为 2.236783018765400576。 那个值然后被转换为十进制。
你需要在任何你想要从数字字符串中得到 Decimal 的地方使用字符串初始值设定项:
let value = Decimal(string: "2.2367830187654")!
XCTAssertEqual(value.rounded(byIncrement: Decimal(string: "0.00000001")!), Decimal(string: "2.23678301")!)
等等
或者您可以使用基于整数的初始值设定项:
let value = Decimal(sign: .plus, exponent: -13, significand: 22367830187654)
在 iOS 15 中有一些新的初始化器不 return 可选项(例如 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")
对于测试代码,这可能很好,但我会非常小心地在生产代码中使用它。
我想将小数点向下舍入到另一个数字的最接近增量。例如,给定 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")
}
}
我不确定为什么小数计算会组成从未存在过的数字,就像上一个断言一样。有没有更简洁或更准确的方法来做到这一点?
你的代码没问题。你只是错误地称呼它。此行不符合您的想法:
let value: Decimal = 2.2367830187654
这相当于:
let value = Decimal(double: Double(2.2367830187654))
该值首先转换为 Double,二进制四舍五入为 2.236783018765400576。 那个值然后被转换为十进制。
你需要在任何你想要从数字字符串中得到 Decimal 的地方使用字符串初始值设定项:
let value = Decimal(string: "2.2367830187654")!
XCTAssertEqual(value.rounded(byIncrement: Decimal(string: "0.00000001")!), Decimal(string: "2.23678301")!)
等等
或者您可以使用基于整数的初始值设定项:
let value = Decimal(sign: .plus, exponent: -13, significand: 22367830187654)
在 iOS 15 中有一些新的初始化器不 return 可选项(例如 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")
对于测试代码,这可能很好,但我会非常小心地在生产代码中使用它。