4

I'm trying to perform a HTTP request to a server: the content is a JSON object, which contains a numeric value for the key "amount". If the "amount" is a value with a decimal digit, e.g. 1.6, the request will contain the value 1.6000000000000001, and this value is not accepted by the Server (the api is Java made and the type is a float . I cannot send a String to the server, since the API that receives the data from me can only accept numbers for the "amount". I tried to perform the request with Siesta Framework or with dataTask, but the result is always the same

this is how I create the request (I omitted the less important parts)

let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject) // jsonObject contains the Double value "amount"
let request = URLRequest(url: url)
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request)    
task.resume()
AleGiovane
  • 172
  • 1
  • 13
  • 2
    could you include some code – Haagenti Jan 18 '18 at 11:20
  • 3
    This is a somewhat fundamental problem with using floating point values in programming https://stackoverflow.com/questions/2100490/floating-point-inaccuracy-examples – Dan F Jan 18 '18 at 11:20
  • Yes, it is. But still this doesn't solve my problem, sorry – AleGiovane Jan 18 '18 at 11:34
  • 1
    You are trying to fix a server problem on the client. The java code on the server should parse the value correctly. If it doesn't do it, it's a server problem. If you require precision, you should use strings (and java `BigDecimal`). – Sulthan Jan 18 '18 at 11:58
  • Thank you. But unfortunately I don't own the server side of the project and in this moment the owner cannot change that part – AleGiovane Jan 18 '18 at 13:47
  • Please show the contents of the jsonObject. – davidethell Jan 18 '18 at 15:16

2 Answers2

9

Without code that fully reproduces the issue, it’s hard to say for sure, but I imagine what you’re seeing is this behavior:

let amount = 1.6  // Double
let jsonObject = ["amount": amount]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject)
String(data: jsonData, encoding: String.Encoding.utf8)

Swift Foundation’s JSON serialization always formats numeric values to their full precision — so the double-precision number 1.6 gets formatted as 1.6000000000000001.

Solution 1: Send a string

You can and should send a string if the server accepts it:

let amount = "1.6"  // String
let jsonObject = ["amount": amount]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject)
String(data: jsonData, encoding: String.Encoding.utf8)

Note that a string is the only correct way to send this value if you are dealing with money or anything else where exact values matter: even if you spell it as 1.6, a standard JSON parser will likely convert it to a floating point on the receiving end.

Solution 2: Use Decimal to alter the formatting

If you just need to format it with less precision to make it pass validation on the server for some reason, you can embed it in the JSON as a Decimal instead of a Double and it will get formatted differently:

let amount = 1.6
let jsonObject = ["amount": Decimal(amount)]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject)
String(data: jsonData, encoding: String.Encoding.utf8)
// {"amount":1.6}

You can even manipulate the Decimal to round to a certain precision.

Note, however, that this does not spare you from floating point precision issues: you are still sending a float according to the JSON spec, and it will still most likely be parsed as a float on the receiving end.

Paul Cantrell
  • 9,175
  • 2
  • 40
  • 48
  • This is a very good answer, thank you. Anyway I solved the problem with a workaround: I converted the json object into a json string using THIS library https://github.com/peheje/JsonSerializerSwift/ : this serializer creates a json string without formatting the 1.6 into 1.6000000000000001 and keeping the value as a Double; then I converted that json string into a Data, and sent the Data using Siesta framework. It's not the best maybe but it works. Thanks again! – AleGiovane Jan 19 '18 at 11:11
  • If the formatting is all you're after, putting a Decimal directly into your dictionary _should_ work with Siesta too. – Paul Cantrell Jan 19 '18 at 15:27
  • With Decimal I solved. Nice and easy, thank you Paul! – AleGiovane Jan 19 '18 at 18:54
0

Without seeing your code it is challenging to offer help, however, one thing to check is whether you are using NumberFormatter to be sure you are getting the correct rounding on your values. Assuming you are pulling the number from a UITextField then you would need something like this:

let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
if let textValue = textField.value {
    var amount = numberFormatter.number(from: textValue)
}
davidethell
  • 11,708
  • 6
  • 43
  • 63