微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在不损失小数精度的情况下格式化从字符串转换的数字

如何解决如何在不损失小数精度的情况下格式化从字符串转换的数字

我有以下JSON有效负载,我需要将其转换为数字并随后进行格式化以进行显示

{
    "kilometers_per_second": "14.4578929636","kilometers_per_hour": "52048.4146691173","miles_per_hour": "32340.8607703746"
}

使用Codable,我创建了以下结构:

struct RelativeVeLocity: Codable,Equatable {
    let kilometersPerSecond: String?
    let kilometersPerHour: String?
    let milesPerHour: String?

    enum CodingKeys: String,CodingKey {
        case kilometersPerSecond = "kilometers_per_second"
        case kilometersPerHour = "kilometers_per_hour"
        case milesPerHour = "miles_per_hour"
    }
}

属性String实例,因为这是API返回的内容,并且我正在学习首次使用视图模型,因此我想使用视图模型来转换String实例在返回格式化的String实例之前先转换成数字。

我的视图模型具有以下结构:

struct RelativeVeLocityviewmodel {
    private let relativeVeLocity: RelativeVeLocity

    init(relativeVeLocity: RelativeVeLocity) {
        self.relativeVeLocity = relativeVeLocity
    }
}

extension RelativeVeLocityviewmodel {
    var formattedKilometersPerHour: String? {
        guard
            let stringValue = relativeVeLocity.kilometersPerHour,let decimalValue = Decimal(string: stringValue),let formatted = NumberFormatter.relativeVeLocityFormatter.string(from: decimalValue as NSNumber)
        else { return nil }
        return formatted
    }

    var formattedKilometersPerSecond: String? {
        guard
            let stringValue = relativeVeLocity.kilometersPerSecond,let formatted = NumberFormatter.relativeVeLocityFormatter.string(from: decimalValue as NSNumber)
        else { return nil }
        return formatted
    }

    var formattedMilesPerHour: String? {
        guard
            let stringValue = relativeVeLocity.kilometersPerSecond,let formatted = NumberFormatter.relativeVeLocityFormatter.string(from: decimalValue as NSNumber)
        else { return nil }
        return formatted
    }
}

如您所见,它将String实例转换为Decimal实例,然后用以下Decimal格式化NumberFormatter实例:

extension NumberFormatter {
    static let relativeVeLocityFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.maximumFractionDigits = .max
        formatter.numberStyle = .decimal
        formatter.usesGroupingSeparator = true
        return formatter
    }()
}

我用于测试视图模型的XCTestCase子类是:

class Tests_RelativeVeLocityviewmodel: XCTestCase {

    let kilometersPerSecond = "14.4578929636"
    let kilometersPerHour = "52048.4146691173"
    let milesPerHour = "32340.8607703746"
    var populatedviewmodel: RelativeVeLocityviewmodel!
    var emptyviewmodel: RelativeVeLocityviewmodel!

    override func setUpWithError() throws {
        try super.setUpWithError()
        let populatedRelativeVeLocity = RelativeVeLocity(
            kilometersPerSecond: kilometersPerSecond,kilometersPerHour: kilometersPerHour,milesPerHour: milesPerHour
        )
        populatedviewmodel = RelativeVeLocityviewmodel(relativeVeLocity: populatedRelativeVeLocity)
        let emptyRelativeVeLocity = RelativeVeLocity(
            kilometersPerSecond: nil,kilometersPerHour: nil,milesPerHour: nil
        )
        emptyviewmodel = RelativeVeLocityviewmodel(relativeVeLocity: emptyRelativeVeLocity)
    }

    override func tearDownWithError() throws {
        emptyviewmodel = nil
        populatedviewmodel = nil
        try super.tearDownWithError()
    }

    func test_RelativeVeLocityviewmodel_ReturnsNilFormattedKilometersPerHour_WhenValueIsMissing() {
        XCTAssertNil(emptyviewmodel.formattedKilometersPerHour)
    }

    func test_RelativeVeLocityviewmodel_ReturnsFormattedKilometersPerHour_WhenValueIsPresent() {
        let expected = "52,048.4146691173"
        XCTAssertEqual(populatedviewmodel.formattedKilometersPerHour,expected)
    }

    func test_RelativeVeLocityviewmodel_ReturnsNilFormattedKilometersPerSecond_WhenValueIsMissing() {
        XCTAssertNil(emptyviewmodel.formattedKilometersPerSecond)
    }

    func test_RelativeVeLocityviewmodel_ReturnsNilFormattedMilesPerHour_WhenValueIsMissing() {
        XCTAssertNil(emptyviewmodel.formattedMilesPerHour)
    }

}

以下测试...

func test_RelativeVeLocityviewmodel_ReturnsFormattedKilometersPerHour_WhenValueIsPresent() {
    let expected = "52,048.4146691173"
    XCTAssertEqual(populatedviewmodel.formattedKilometersPerHour,expected)
}

...产生以下故障:

XCTAssertEqual Failed: ("Optional("52,048.414669")") is not equal to ("Optional("52,048.4146691173")")

我知道我可以使用XCTAssertEqual(_:_:accuracy:_:file:line:),但是我想保留所有十进制值。

我做错了什么,导致格式化结果由于失去值的精度而被舍入?

解决方法

尝试一下:

class MyProjectTests: XCTestCase {

    func testExample() throws {
        let stringValue = "52048.12345678911111"
        let decimalValue = Decimal(string: stringValue)!
        let formatted = NumberFormatter.relativeVelocityFormatter(maxFractionDigits: decimalValue.significantFractionalDecimalDigits).string(from: decimalValue as NSNumber)
        XCTAssert(formatted == stringValue)
    }
}

extension NumberFormatter {
    static func relativeVelocityFormatter(maxFractionDigits: Int) -> NumberFormatter {
        let formatter = NumberFormatter()
        formatter.maximumFractionDigits = maxFractionDigits
        formatter.numberStyle = .none
        formatter.usesGroupingSeparator = true
        return formatter
    }
}

extension Decimal {
    var significantFractionalDecimalDigits: Int {
        return max(-exponent,0)
    }
}

无论如何,总会有一个限制:

enter image description here

33个十进制数字。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。