swift - 如何在不损失小数精度的情况下格式化从字符串转换的数字
问题描述
我有以下 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 decimalValue = Decimal(string: stringValue),
let formatted = NumberFormatter.relativeVelocityFormatter.string(from: decimalValue as NSNumber)
else { return nil }
return formatted
}
var formattedMilesPerHour: String? {
guard
let stringValue = relativeVelocity.kilometersPerSecond,
let decimalValue = Decimal(string: stringValue),
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)
}
}
无论如何,总是有一个限制:
33 位十进制数字。
推荐阅读
- python - 调用函数并非所有函数都使用 argparse 快捷方式
- forms - Spring Boot - 使用@PathVariable 将数据放入更新表单
- python - 如何实现虚拟属性层次结构?
- authentication - 将逻辑应用凭据传递给自定义 SharePoint 客户端 C# 函数应用 SharePoint
- sql - 带有否定 WHERE 条件的 SQL JOIN
- ruby - 在 ActiveAdmin 中根据需要设置字段
- python - 删除 XML 标记外的字符
- bro - 是否可以使用 Zeek 检查 TCP 保留位?
- joomla - 如何从弹出窗口的菜单项中打开文章?
- sql - 两行的部分连接