Swift precision problem 2

"This is the 12th day of my participation in the November Update Challenge. For details of the event, please refer to: 2021 Last Update Challenge ".

Difference between NSDecimalNumber and Decimal

NSDecimalNumberNSNumberIs a subclass of , NSNumbermore powerful than , rounding, rounding, automatically removing the useless 0 in front of the value after input, etc. Due to the high NSDecimalNumberprecision , it will take more time than the basic data type, so it needs to be weighed, Apple official It is recommended to be used in currency and scenarios that require high precision.

Usually we will use NSDecimalNumberHandlerthis formatter to set the format that needs to be constrained, and then construct the requiredNSDecimalNumber

let ouncesDecimal: NSDecimalNumber = NSDecimalNumber(value: doubleValue)
let behavior: NSDecimalNumberHandler = NSDecimalNumberHandler(roundingMode: mode,
                                                              scale: Int16(decimal),
                                                              raiseOnExactness: false,
                                                              raiseOnOverflow: false,
                                                              raiseOnUnderflow: false,
                                                              raiseOnDivideByZero: false)
let roundedOunces: NSDecimalNumber = ouncesDecimal.rounding(accordingToBehavior: behavior)
复制代码

NSDecimalNumberIt is Decimalbasically a seamless bridge with . It Decimalis a value type Structand NSDecimalNumbera reference type Class. It seems NSDecimalNumberthat the setting function is more abundant, but if only the number of digits and the rounding method are required, it Decimalcan be fully satisfied, and the performance will be better. , so I think it should NSDecimalNumberonly Decimalbe considered as a backup if a feature cannot be implemented.

In general, theNSDecimalNumber relationship with is similar to the relationship with .DecimalNSStringString

Decimalthe correct way to use

Correct use of jsondeserialization Decimalto assign values ​​-- useObjectMapper

When we declare a Decimalproperty , and then use a jsonstring to assign it, we will find that the precision is still lost, why is there such a result?

struct Money: Codable {
    let amount: Decimal
    let currency: String
}

let json = "{"amount": 9021.234891,"currency": "CNY"}"
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()

let money = try! decoder.decode(Money.self, from: jsonData)
print(money.amount)
复制代码

image.png

The answer is simple: what we use is used JSONDecoder()internally JSONSerialization()for deserialization, and its logic is very simple. When it encounters 9021.234891this number, it will not hesitate to regard it as a Doubletype, and then Doubleconvert it to DecimalIt can be successful, But at this time, the precision has been Doublelost and the converted Decimaltype is naturally lost.

For this problem, we must be able to control its deserialization process. My current option is to use ObjectMapperit, which can flexibly control the serialization and deserialization process using custom rules.

ObjectMapperIt is not supported Decimalby , we can customize a supported Decimaltype TransformType, as follows:

open class DecimalTransform: TransformType {
    public typealias Object = Decimal
    public typealias JSON = Decimal

    public init() {}

    open func transformFromJSON(_ value: Any?) -> Decimal? {
        if let number = value as? NSNumber {
            return Decimal(string: number.description)
        } else if let string = value as? String {
            return Decimal(string: string)
        }
        return nil
    }

    open func transformToJSON(_ value: Decimal?) -> Decimal? {
        return value
    }
}
复制代码

Then apply this TransformTypeto the properties we need to transform

struct Money: Mappable {
    var amount: Decimal?
    var currency: String?

    init() { }
    init?(map: Map) { }

    mutating func mapping(map: Map) {
        amount <- (map["amount"], DecimalTransform())
        currency <- map["currency"]
    }
}
复制代码

image.png

Correct use Decimalof initialization

DecimalThere are multiple initialization methods. We can pass in integer values, floating point values, and strings for initialization. I think the correct initialization method should be to use strings.

image.png

The above picture should be very simple and clear why I think so. The reason is similar to the previous deserialization problem, but also because when we passed Doublein , Swift carried it once, and this time the carry caused it Precision is lost, according to the Doubleinitialization Decimal, it is not difficult to understand that this Decimalis the loss of precision

refer to

Decoding money from JSON in Swift

Guess you like

Origin juejin.im/post/7030247481703153671