【问题标题】:NSJSONSerialization with small decimal numbers带有小十进制数的 NSJSONSerialization
【发布时间】:2016-01-28 05:36:25
【问题描述】:

我试图将0.81 的Double 值包含在NSJSONSerialization 生成的一些JSON 中。代码如下:

let jsonInput = [ "value": 0.81 ]
let data = try NSJSONSerialization.dataWithJSONObject(jsonInput, options: NSJSONWritingOptions.PrettyPrinted)
let json = NSString(data: data, encoding: NSUTF8StringEncoding)!
print( json )

输出是:

{
  "value" : 0.8100000000000001
}

但我想看到的是:

{
  "value" : 0.81
}

我怎样才能让NSJSONSerialization 这样做?

这里让我感到困惑的另一件事是 Swift 对 64 位 Double 的处理。就像在操场上一样,我也可以这样做:

let eightOne:Double = 0.81
"\(eightOne)"
print( eightOne )

然后根据需要输出:

0.81

即使在操场上,就内部表示而言,它仍将eightOne 显示为0.8100000000000001。但是在这里,当它转换为字符串时,它会切断其余部分。

我肯定这已经解决了,因为您需要对任何类型的财务处理进行排序(例如,在 Java 中,我们知道在涉及财务价值时我们只使用 BigDecimals)。

请帮忙。 :)

注意:这里的重点是序列化为 JSON。不仅仅是简单地拨打NSString( format: "%\(0.2)f", 0.81)。

【问题讨论】:

    标签: json swift


    【解决方案1】:

    用于精确的 base-10 算术(最多 38 个有效数字) 你可以使用NSDecimalNumber:

    let jsonInput = [ "value":  NSDecimalNumber(string: "0.81") ]
    

    或

    let val = NSDecimalNumber(integer: 81).decimalNumberByDividingBy(NSDecimalNumber(integer: 100))
    let jsonInput = [ "value":  val ]
    

    然后

    let data = try NSJSONSerialization.dataWithJSONObject(jsonInput, options: NSJSONWritingOptions.PrettyPrinted)
    let json = NSString(data: data, encoding: NSUTF8StringEncoding)!
    print( json )
    

    产生输出

    {
      "value" : 0.81
    }
    

    【讨论】:

      【解决方案2】:

      手动转换

      您需要将 Double 转换为 Decimal 以在序列化时保持其预期的字符串表示形式。

      避免 16 位精度的一种方法可能是用 15 的比例四舍五入:

      (0.81 as NSDecimalNumber).rounding(accordingToBehavior: NSDecimalNumberHandler(roundingMode: .plain, scale: 15, raiseOnExactness: false, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true)) as Decimal
      

      用于自动转换的 JSONSerialization 扩展

      要自动递归地对 JSON 对象中的所有 Double 值执行此操作,无论是字典还是数组,您可以使用:

      import Foundation
      
      /// https://stackoverflow.com/q/35053577/1033581
      extension JSONSerialization {
      
          /// Produce Double values as Decimal values.
          open class func decimalData(withJSONObject obj: Any, options opt: JSONSerialization.WritingOptions = []) throws -> Data {
              return try data(withJSONObject: decimalObject(obj), options: opt)
          }
      
          /// Write Double values as Decimal values.
          open class func writeDecimalJSONObject(_ obj: Any, to stream: OutputStream, options opt: JSONSerialization.WritingOptions = [], error: NSErrorPointer) -> Int {
              return writeJSONObject(decimalObject(obj), to: stream, options: opt, error: error)
          }
      
          fileprivate static let roundingBehavior = NSDecimalNumberHandler(roundingMode: .plain, scale: 15, raiseOnExactness: false, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true)
      
          fileprivate static func decimalObject(_ anObject: Any) -> Any {
              let value: Any
              if let n = anObject as? [String: Any] {
                  // subclassing children
                  let dic = DecimalDictionary()
                  n.forEach { dic.setObject($1, forKey: $0) }
                  value = dic
              } else if let n = anObject as? [Any] {
                  // subclassing children
                  let arr = DecimalArray()
                  n.forEach { arr.add($0) }
                  value = arr
              } else if let n = anObject as? NSNumber, CFNumberGetType(n) == .float64Type {
                  // converting precision for correct decimal output
                  value = NSDecimalNumber(value: anObject as! Double).rounding(accordingToBehavior: roundingBehavior)
              } else {
                  value = anObject
              }
              return value
          }
      }
      
      private class DecimalDictionary: NSDictionary {
          let _dictionary: NSMutableDictionary = [:]
      
          override var count: Int {
              return _dictionary.count
          }
          override func keyEnumerator() -> NSEnumerator {
              return _dictionary.keyEnumerator()
          }
          override func object(forKey aKey: Any) -> Any? {
              return _dictionary.object(forKey: aKey)
          }
      
          func setObject(_ anObject: Any, forKey aKey: String) {
              let value = JSONSerialization.decimalObject(anObject)
              _dictionary.setObject(value, forKey: aKey as NSString)
          }
      }
      
      private class DecimalArray: NSArray {
          let _array: NSMutableArray = []
      
          override var count: Int {
              return _array.count
          }
          override func object(at index: Int) -> Any {
              return _array.object(at: index)
          }
      
          func add(_ anObject: Any) {
              let value = JSONSerialization.decimalObject(anObject)
              _array.add(value)
          }
      }
      

      用法

      JSONSerialization.decimalData(withJSONObject: [ "value": 0.81 ], options: [])
      

      注意

      如果您需要微调十进制格式,您可以在Specify number of decimals when serializing currencies with JSONSerialization 上查看 Eneko Alonso 的答案。

      【讨论】:

      • "((0.81 as NSNumber).decimalValue)" 返回 "0.8100000000000001" Xcode 11.3.1
      • @AntonPlebanovich 哦,两年后可能发生了一些变化。好吧,我用不同的解决方法编辑了消息。
      【解决方案3】:

      如果您有使用 'NSDecimalNumber' 的需求,建议封装以方便使用,减少错误。

      这里的Demo供大家参考,使用简单。希望能帮到你!

      switch (operatorType) {
        case 0:
            resultNumber = SNAdd(_cardinalNumberTextField.text, _complementNumberTextField.text);
            break;
        case 1:
            resultNumber = SNSub(_cardinalNumberTextField.text, _complementNumberTextField.text);
            break;
        case 2:
            resultNumber = SNMul(_cardinalNumberTextField.text, _complementNumberTextField.text);
            break;
        case 3:
            resultNumber = SNDiv(_cardinalNumberTextField.text, _complementNumberTextField.text);
            break;
       }
      

      Github:https://github.com/ReverseScale/DecimalNumberDemo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-09-08
        • 1970-01-01
        • 2015-05-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多