Swift 4: Strange Double and Float behaviour - macos

Basically the problem is that in this example:
let d1 = NSNumber(value: 1.4);
let d2 = d1.doubleValue;
let f1 = NSNumber(value: Float(1.4));
let f2 = d1.floatValue;
d1 results 1.4
d2 results 1.3999999999999999
f1 results 1.4
f2 results 1.3999999999999998
Does anyone know why is that?
I'm trying to parse JSON file like:
{"name": "something", "version": 1.4}
with the following code:
let json = try (JSONSerialization.jsonObject(with: someData) as? [String: Any])!;
let version: Double = (json["version"] as! NSNumber).doubleValue;
OR
let version: Double = json["version"] as! Double;
OR
let version: Double = json["version"] as! Float;
And I just can't get 1.4...
Rounding the number is not a solution for me, because I want to write back this number to JSON file, that will be parsed by other programs/languages and needs to be exactly 1.4 in the file.
Any suggestions?
UPDATE: The problem is only with 1.1 and 1.4. There is no problem with 1.2, 1.3, 1.5
UPDATE 2: Serialization code:
let jsonDict: Dictionary<String,Any> = [
"name" : name,
"version" : version
];
let data = try? JSONSerialization.data(withJSONObject: jsonDict, options: []);
let jsonString = String(data:data, encoding:.utf8);

Ok, just to finalise the discussion.
At the end Decimal type did the trick. So I changed all variable references to Decimal and NOT NSDecimalNumber, because I got error that it doesn't comply with Codable and Decodable protocols. Maybe there is a workaround for this, but the easiest solution is just to stick with Decimal.
I would like to thanks to #JamesBucanek and #EricPostpischil for joining the discussion and help resolving this issue !!!

I had the same story.
I need to sent/receive float values using API endpoint. So I followed advice and changed Double to Decimal. It worked fine for encoding data, but not for decoding.
let value = "0.0006"
let decimal = Decimal(string: value)!
print(decimal) // 0.0006 OK
let jsonData = try JSONEncoder().encode(decimal)
print(String(data: jsonData, encoding: .utf8)!) // 0.0006 OK
let decocdedDecimal = try JSONDecoder().decode(Decimal.self, from: jsonData)
print(decocdedDecimal) // 0.0005999999999999998976 NOOOOOO!!!
However decode to Double works fine.
let decocdedDouble = try JSONDecoder().decode(Double.self, from: jsonData)
print(decocdedDouble) // 0.0006 OK
Also as was mentioned in answers above - Decimal should be inited with String to be encoded correctly
let decimal = Decimal(0.0006)
print(decimal) // 0.0005999999999999998976, it will be encoded same way
And ofcourse Double encoding not working as expected, otherwise we didn't have such issue.
// 0.00059999999999999995 for all cases ofcourse
print(String(data: try JSONEncoder().encode(0.0006), encoding: .utf8)!)
print(String(data: try JSONEncoder().encode(decocdedDouble), encoding: .utf8)!)
print(String(data: try JSONEncoder().encode(Double(value)!), encoding: .utf8)!)
So my dirty solution for now is to use wrapper for Double values. It will decode value as Double, but encode as Decimal(string:)
struct CodableDouble: Codable {
var value: Double
init(_ value: Double) {
self.value = value
}
init(from decoder: Decoder) throws {
value = try decoder.singleValueContainer().decode(Double.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let decimal = Decimal(string: "\(value)") ?? 0
try container.encode(decimal)
}
}
But I still don't understand what is the correct way to handle this issue. (except not using float values)

Related

Xcode 8.1 swift 3 take forever to compile this code

I have this class in a project which previously use swift 2.3. When i migrated the project to swift 3, xcode took forever to compile and i saw it stuck at this class. I can not build the whole project because of this class. Is there a way to modify this class so the project can be built, it took Xcode forever to compile this piece of code. If i removed several properties from MyClass, Xcode will quickly compile again. Anyone has any idea on how to solve this problem?
import Foundation
class MyClass: NSObject {
var id: String = ""
var uid: String = ""
var uname: String = ""
var fname: String = ""
var txt: String = ""
var hay: Float = 0
var flag = false
var long: Double = 0
var lat: Double = 0
var altitude: Double = 0
var course: Double = 0
var speed: Double = 0
var lname: String = ""
var city: String = ""
var country: String = ""
var sublocal: String = ""
var subarea: String = ""
var thumb: String = ""
var trash = false
var date: Double = 0
var updated: Double = 0
var furl: String = ""
func toAnyObject() -> Any {
return [
"id": id,
"uid": uid,
"uname": uname,
"fname": fname,
"txt": txt,
"hay": hay,
"flag": flag,
"long": long,
"lat": lat,
"altitude": altitude,
"course": course,
"speed": speed,
"lname": lname,
"city": city,
"country": country,
"sublocal": sublocal,
"trash": trash,
"subarea": subarea,
"thumb": thumb,
"date": date,
"updated": updated,
"furl": furl
]
}
}
Rewrite without the big dictionary literal. So:
func toAnyObject() -> Any {
var d = [String:Any]()
d["id"] = id
d["uid"] = uid
// ... and so on ...
return d
}
If you're not doing so already adding a -Xfrontend -debug-time-function-bodies compiler flag to your project will list the time required to compile each function. That can be a useful way to identify what exactly is slow in your build. (See http://irace.me/swift-profiling or https://thatthinginswift.com/debug-long-compile-times-swift/).
In your case the compiler is probably struggling to determine the type of your dictionary literal. Looking at each key and value and then trying to find the most appropriate common type for all of them. If you specified a type then I expect the compiler will only need to verify that your literal matches that type and compile much more quickly:
let result: [String: Any] = ["id": id, ...]
return result
Sometimes the compiler slows down when you do implicit typing. If you Explicitly add the type information then the compiler will not need to calculate it. I can see that the properties of your class mostly have type information but not all of them. In your toAnyObject method, it seems like you want your object represented as a dictionary, yet you are converting it to type Any.
You are making a dictionary literal and offering no type information. Explicitly casting with "as" can help a lot.
When you convert something to type Any, Objective-C interprets that as id. Normally a swift dictionary would be bridged to an NSDictionary for Objective-c, but you are forcing it to be of type Any. What reason would the compiler have to bridge this to an NSDictionary? It probably boxes it since it thinks it is a struct and objective-c can't use swift structs.
Try to not confuse the compiler.

xcode: need to convert strings to double and back to string

this is my line of code.
budgetLabel.text = String((budgetLabel.text)!.toInt()! - (budgetItemTextBox.text)!.toInt()!)
the code works, but when I try to input a floating value into the textbox the program crashes. I am assuming the strings need to be converted to a float/double data type. I keep getting errors when i try to do that.
In Swift 2 there are new failable initializers that allow you to do this in more safe way, the Double("") returns an optional in cases like passing in "abc" string the failable initializer will return nil, so then you can use optional-binding to handle it like in the following way:
let s1 = "4.55"
let s2 = "3.15"
if let n1 = Double(s1), let n2 = Double(s2) {
let newString = String( n1 - n2)
print(newString)
}
else {
print("Some string is not a double value")
}
If you're using a version of Swift < 2, then old way was:
var n1 = ("9.99" as NSString).doubleValue // invalid returns 0, not an optional. (not recommended)
// invalid returns an optional value (recommended)
var pi = NSNumberFormatter().numberFromString("3.14")?.doubleValue
Fixed: Added Proper Handling for Optionals
let budgetLabel:UILabel = UILabel()
let budgetItemTextBox:UITextField = UITextField()
budgetLabel.text = ({
var value = ""
if let budgetString = budgetLabel.text, let budgetItemString = budgetItemTextBox.text
{
if let budgetValue = Float(budgetString), let budgetItemValue = Float(budgetItemString)
{
value = String(budgetValue - budgetItemValue)
}
}
return value
})()
You need to be using if let. In swift 2.0 it would look something like this:
if let
budgetString:String = budgetLabel.text,
budgetItemString:String = budgetItemTextBox.text,
budget:Double = Double(budgetString),
budgetItem:Double = Double(budgetItemString) {
budgetLabel.text = String(budget - budgetItem)
} else {
// If a number was not found, what should it do here?
}

Convert String "10.20" to Double and then to Int at once in Swift 2.0

I'm trying to convert the String "10.20" to Int without converting it to Double before.
I want to get the value: 10
At the moment what is working is:
let valueString = "10.20"
let valueInt = Int(Double(valueString)!)
print(valueInt)
But, is there any better way to do it?
I was trying first to do it with this command but was returning nil:
let valueString = "10.20"
let valueInt = Int(valueString)
print(valueInt)
Thanks!
You could just put an extension on the Int type that makes it StringLiteralConvertable, producing the expected result via a Float cast behind the scenes.

Format Numbers in Textfields using Swift

I am trying to format a number from a UITextfield, as its being typed, to a decimal with commas.
I have done so with the following code:
#IBAction func editingDidBegin(sender : AnyObject)
{
costField.addTarget(self, action: Selector("textFieldDidChange:"), forControlEvents: UIControlEvents.EditingChanged)
}
func textFieldDidChange(theTextField:UITextField) -> Void
{
var textFieldText = theTextField.text.stringByReplacingOccurrencesOfString(",", withString: " ", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text.startIndex, end: theTextField.text.endIndex))
var formatter:NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
var formattedOutput = formatter.stringFromNumber(textFieldText.bridgeToObjectiveC().integerValue)
costField.text = formattedOutput
}
The problem with this, is after four digits are entered, everything after the comma is deleted. For example if I enter 4000 it formats to 4,000, then if I type another number like 8 it reformats to 48.
Is there another way I can format this, maybe through IB or how can I fix the code?
Replace the line with:
var textFieldText = theTextField.text.stringByReplacingOccurrencesOfString(",", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text.startIndex, end: theTextField.text.endIndex))
(I only removed the space between the double quotes).
Fact is, NSNumberFormatter doesn't like the added spaces in the string.
Works fine afterwards.
I know I am late to the party but this worked well for me.
var phoneNumber = " 1 (888) 555-5551 "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))
It takes out the spaces and strips out the non decimal numeric characters.
The end result is "1888555551"
I've updated this answer to the newest version of swift. This borrows 90% from the two answers above however, also accounts for nil exception from the textfield when the textfield is cleared.
func textFieldDidChangeCommas(theTextField:UITextField) -> Void
{
if theTextField.text != nil {
var textFieldText = theTextField.text!.stringByReplacingOccurrencesOfString(",", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text!.startIndex, end: theTextField.text!.endIndex))
var formatter:NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
if textFieldText != "" {
var formattedOutput = formatter.stringFromNumber(Int(textFieldText)!)
costField.text = formattedOutput
}
}
}

RNCryptor: AES128CBC decrypting in Swift

How can I decrypt NSData with RNCryptor (AES128CBC)?
I already tried to understand the documentation: https://github.com/RNCryptor/RNCryptor-Spec/blob/master/draft-RNCryptor-Spec-v4.0.md
Edit:
class Decription{
func AES128(message: String, key: String, iv: String){
let keyData: NSData! = (key as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
let ivData: NSData! = (iv as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
let data: NSData! = (message as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
let cryptData = NSMutableData(length: Int(data.length) + kCCBlockSizeAES128)!
let keyLength = size_t(kCCKeySizeAES256)
let operation: CCOperation = UInt32(kCCDecrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(
operation,
algoritm,
options,
keyData.bytes,
keyLength,
ivData.bytes,
data.bytes, data.length,
cryptData.mutableBytes,
cryptData.length,
&numBytesEncrypted
)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
print("Decrypted Result = \(NSString(data: cryptData, encoding: NSUTF8StringEncoding))")
} else {
print("Error: \(cryptStatus)")
}
}
}
I changed the code snippet #zaph linked to. You can see the result above.
NSString(data: cryptData, encoding: NSUTF8StringEncoding) results nil. What is the problem?
First you encrypt using RNCryptor, not some other method because RNCryptor is more than just AES encryption, it an entire secure encryption method including key derivation and authentication. See the RNCryptor-Spec for more information.
If you just want decryption use Common Crypto with Swift, see this SO Answer for example code.
Note: The sample code is just that, it should not be used as-is in production code. In particular ECB mode is not secure, use CBC with a random iv and consider adding authentication and key derivation to the password such as PBKDF2.

Resources