Swift 4 - efficient conversion of Double to big-endian - swift

I need to convert a Double to big-endian in order to write it to a file, using an oil-industry binary file standard, that was originally defined for IBM half-inch 9 track tapes in the 1970s!
I need really efficient Swift 4 code, because this conversion is inside two nested-loops and will be executed upwards of 100,000 times.

You can create an UInt64 containing the big-endian representation
of the Double with
let value = 1.0
var n = value.bitPattern.bigEndian
In order to write that to a file you might need to convert it
to Data:
let data = Data(buffer: UnsafeBufferPointer(start: &n, count: 1))
print(data as NSData) // <3ff00000 00000000>
If many contiguous floating point values are written to the file
then it would be more effective to create an [UInt64] array
with the big-endian representations and convert that to Data,
for example
let values = [1.0, 2.0, 3.0, 4.0]
let array = values.map { $0.bitPattern.bigEndian }
let data = array.withUnsafeBufferPointer { Data(buffer: $0) }
(All the above compiles with Swift 3 and 4.)

I successfully implemented Martin's array suggestion. I decided I should use some "interesting" test values and one thing led to another! Here's my test playground. I hope it is of interest:
//: Playground - noun: a place where people can play
import UIKit
func convert(doubleArray: [Double]) {
let littleEndianArray = doubleArray.map { $0.bitPattern}
var data = littleEndianArray.withUnsafeBufferPointer { Data(buffer: $0) }
print("Little-endian : ", data as NSData)
// Convert and display the big-endian bytes
let bigEndianArray = doubleArray.map { $0.bitPattern.bigEndian }
data = bigEndianArray.withUnsafeBufferPointer { Data(buffer: $0) }
print("Big-endian : ", data as NSData)
}
// Values below are from:
// https://en.wikipedia.org/wiki/Double-precision_floating-point_format
let nan = Double.nan
let plusInfinity = +1.0 / 0.0
let maxDouble = +1.7976931348623157E308
let smallestNumberGreaterThanOne = +1.0000000000000002
let plusOne = +1.0
let maxSubnormalPositiveDouble = +2.2250738585072009E-308
let minSubnormalPositiveDouble = +4.9E-324
let plusZero = +0.0
let minusZero = -0.0
let maxSubnormalNegativeDouble = -4.9E-324
let minSubnormalNegativeDouble = -2.2250738585072009E-308
let minusOne = -1.0
let largestNumberLessThanOne = -1.0000000000000002
let minDouble = -1.7976931348623157E308
let minusInfinity = -1.0 / 0.0
let smallestNumber = "+1.0000000000000002"
let largestNumber = "-1.0000000000000002"
print("\n\nPrint little-endian and big-endian Doubles")
print("\n\nDisplay: NaN and +0.0 to +1.0")
print(" Min. Subnormal Max. Subnormal")
print(" Not a Number Plus Zero Positive Double Positive Double Plus One")
print(String(format: "Decimal : NaN %+8.6e %+8.6e %+8.6e %+8.6e", plusZero, minSubnormalPositiveDouble, maxSubnormalPositiveDouble, plusOne))
var doubleArray = [nan, plusZero, minSubnormalPositiveDouble, maxSubnormalPositiveDouble, plusOne]
convert(doubleArray: doubleArray)
print("\n\nDisplay: +1.0 to +Infinity")
print(" Smallest Number ")
print(" Plus One Greater Than 1.0 Max. Double +Infinity")
print(String(format: "Decimal : %+8.6e \(smallestNumber) %+8.6e%+8.6e", plusOne, maxDouble, plusInfinity))
doubleArray = [plusOne, smallestNumberGreaterThanOne, maxDouble, plusInfinity]
convert(doubleArray: doubleArray)
print("\n\nDisplay: NaN and -0.0 to -1.0")
print(" Min. Subnormal Max. Subnormal")
print(" Not a Number Minus Zero Negative Double Negative Double Minus One")
print(String(format: "Decimal : NaN %+8.6e %+8.6e %+8.6e %+8.6e", minusZero, maxSubnormalNegativeDouble, minSubnormalNegativeDouble, minusOne))
doubleArray = [nan, minusZero, maxSubnormalNegativeDouble, minSubnormalNegativeDouble, minusOne]
convert(doubleArray: doubleArray)
print("\n\nDisplay: -1.0 to -Infinity")
print(" Smallest Number ")
print(" Minus One Less Than -1.0 Min. Double -Infinity")
print(String(format: "Decimal : %+8.6e \(largestNumber) %+8.6e%+8.6e", minusOne, minDouble, minusInfinity))
doubleArray = [minusOne, largestNumberLessThanOne, minDouble, minusInfinity]
convert(doubleArray: doubleArray)

Related

How to obtain precision of two numbers after the decimal point but without rounding the double in SWIFT [duplicate]

This question already has answers here:
How to truncate decimals to x places in Swift
(10 answers)
Closed 3 years ago.
I am new with swift and I need help. I want to get first two digits after the decimal point, for example -
1456.456214 -> 1456.45
35629.940812 -> 35629.94
without rounding the double to next one.
Try the below code
let num1 : Double = 1456.456214
let num2 : Double = 35629.940812
let numberFormatter = NumberFormatter()
numberFormatter.minimumFractionDigits = 2
numberFormatter.maximumFractionDigits = 2
numberFormatter.roundingMode = .down
let str = numberFormatter.string(from: NSNumber(value: num1))
let str2 = numberFormatter.string(from: NSNumber(value: num2))
print(str)
print(str2)
Output
1456.45
35629.94
To keep it a double you can do
let result = Double(Int(value * 100)) / 100.0
or, as #vacawama pointed out, use floor instead
let result = floor(value * 100) / 100
extension Double {
func truncate(places : Int)-> Double
{
return Double(floor(pow(10.0, Double(places)) * self)/pow(10.0, Double(places)))
}
}
and use this like as;
let ex: Double = 35629.940812
print(ex.truncate(places: 2)) //35629.94
let ex1: Double = 1456.456214
print(ex1.truncate(places: 2)) //1456.45

When rounding swift double it shows different numbers

When I got two numbers, like 5.085 and 70.085. My code rounds the first number to 5.09, but the second one it goes to 70.08. For some reason, when making let aux1 = aux * 100 the value goes to 7008.49999999. Any one have the solution to it?
Here is my code:
let aux = Double(value)!
let aux1 = aux * 100
let aux2 = (aux1).rounded()
let number = aux2 / 100
return formatter.string(from: NSNumber(value: number))!
If you want to format the Double by rounding it's fraction digits. Try't:
First, implement this method
func formatDouble(_ double: Double, withFractionDigits digits: Int) -> String{
let formatter = NumberFormatter()
formatter.maximumFractionDigits = digits
let string = formatter.string(from: (NSNumber(floatLiteral: double)))!
return string
/*if you want a Double instead of a String, change the return value and uncomment the bellow lines*/
//let number = formatter.number(from: string)!
//return number.doubleValue
}
after, you can call't that way
let roundedNumber = formatDouble(Double(value)!, withFractionDigits: 2)

Swift: Byte array to decimal value

In my project, I communicate with a bluetooth device, the bluetooth device must send me a timestamp second, I received in byte:
[2,6,239]
When I convert converted to a string:
let payloadString = payload.map {
String(format: "%02x", $0)
}
Output:
["02", "06","ef"]
When I converted from the website 0206ef = 132847 seconds
How can I directly convert my aray [2,6,239] in second (= 132847 seconds)?
And if it's complicated then translate my array ["02", "06,"ef"] in second (= 132847 seconds)
The payload contains the bytes of the binary representation of the value.
You convert it back to the value by shifting each byte into its corresponding position:
let payload: [UInt8] = [2, 6, 239]
let value = Int(payload[0]) << 16 + Int(payload[1]) << 8 + Int(payload[2])
print(value) // 132847
The important point is to convert the bytes to integers before shifting, otherwise an overflow error would occur. Alternatively,
with multiplication:
let value = (Int(payload[0]) * 256 + Int(payload[1])) * 256 + Int(payload[2])
or
let value = payload.reduce(0) { $0 * 256 + Int($1) }
The last approach works with an arbitrary number of bytes – as long as
the result fits into an Int. For 4...8 bytes you better choose UInt64
to avoid overflow errors:
let value = payload.reduce(0) { $0 * 256 + UInt64($1) }
payloadString string can be reduced to hexStr and then converted to decimal
var payload = [2,6,239];
let payloadString = payload.map {
String(format: "%02x", $0)
}
//let hexStr = payloadString.reduce(""){$0 + $1}
let hexStr = payloadString.joined()
if let value = UInt64(hexStr, radix: 16) {
print(value)//132847
}

Formatting decimal places with unknown number

I'm printing out a number whose value I don't know. In most cases the number is whole or has a trailing .5. In some cases the number ends in .25 or .75, and very rarely the number goes to the thousandths place. How do I specifically detect that last case? Right now my code detects a whole number (0 decimal places), exactly .5 (1 decimal), and then reverts to 2 decimal spots in all other scenarios, but I need to go to 3 when it calls for that.
class func getFormattedNumber(number: Float) -> NSString {
var formattedNumber = NSString()
// Use the absolute value so it works even if number is negative
if (abs(number % 2) == 0) || (abs(number % 2) == 1) { // Whole number, even or odd
formattedNumber = NSString(format: "%.0f", number)
}
else if (abs(number % 2) == 0.5) || (abs(number % 2) == 1.5) {
formattedNumber = NSString(format: "%.1f", number)
}
else {
formattedNumber = NSString(format: "%.2f", number)
}
return formattedNumber
}
A Float uses a binary (IEEE 754) representation and cannot represent
all decimal fractions precisely. For example,
let x : Float = 123.456
stores in x the bytes 42f6e979, which is approximately
123.45600128173828. So does x have 3 or 14 fractional digits?
You can use NSNumberFormatter if you specify a maximum number
of decimal digits that should be presented:
let fmt = NSNumberFormatter()
fmt.locale = NSLocale(localeIdentifier: "en_US_POSIX")
fmt.maximumFractionDigits = 3
fmt.minimumFractionDigits = 0
println(fmt.stringFromNumber(123)!) // 123
println(fmt.stringFromNumber(123.4)!) // 123.4
println(fmt.stringFromNumber(123.45)!) // 123.45
println(fmt.stringFromNumber(123.456)!) // 123.456
println(fmt.stringFromNumber(123.4567)!) // 123.457
Swift 3/4 update:
let fmt = NumberFormatter()
fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.maximumFractionDigits = 3
fmt.minimumFractionDigits = 0
print(fmt.string(for: 123.456)!) // 123.456
You can use %g to suppress trailing zeros. Then I think you do not need to go through the business of determining the number of places. Eg -
var num1:Double = 5.5
var x = String(format: "%g", num1) // "5.5"
var num2:Double = 5.75
var x = String(format: "%g", num2) // "5.75"
Or this variation where the number of places is specified. Eg -
var num3:Double = 5.123456789
var x = String(format: "%.5g", num3) // "5.1235"
My 2 cents ;) Swift 3 ready
Rounds the floating number and strips the trailing zeros to the required minimum/maximum fraction digits.
extension Double {
func toString(minimumFractionDigits: Int = 0, maximumFractionDigits: Int = 2) -> String {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.minimumFractionDigits = minimumFractionDigits
formatter.maximumFractionDigits = maximumFractionDigits
return formatter.string(from: self as NSNumber)!
}
}
Usage:
Double(394.239).toString() // Output: 394.24
Double(394.239).toString(maximumFractionDigits: 1) // Output: 394.2
If you want to print a floating point number to 3 decimal places, you can use String(format: "%.3f"). This will round, so 0.10000001 becomes 0.100, 0.1009 becomes 0.101 etc.
But it sounds like you don’t want the trailing zeros, so you might want to trim them off. (is there a way to do this with format? edit: yes, g as #simons points out)
Finally, this really shouldn’t be a class function since it’s operating on primitive types. Better to either make it a free function, or perhaps extend Double/Float:
extension Double {
func toString(#decimalPlaces: Int)->String {
return String(format: "%.\(decimalPlaces)g", self)
}
}
let number = -0.3009
number.toString(decimalPlaces: 3) // -0.301

Split a double by dot to two numbers

So I am trying to split a number in swift, I have tried searching for this on the internet but have had no success. So first I will start with a number like:
var number = 34.55
And from this number, I want to create two separate number by splitting from the dot. So the output should be something like:
var firstHalf = 34
var secondHalf = 55
Or the output can also be in an array of thats easier. How can I achieve this?
The easiest way would be to first cast the double to a string:
var d = 34.55
var b = "\(d)" // or String(d)
then split the string with the global split function:
var c = split(b) { $0 == "." } // [34, 55]
You can also bake this functionality into a double:
extension Double {
func splitAtDecimal() -> [Int] {
return (split("\(self)") { $0 == "." }).map({
return String($0).toInt()!
})
}
}
This would allow you to do the following:
var number = 34.55
print(number.splitAtDecimal()) // [34, 55]
Well, what you have there is a float, not a string. You can't really "split" it, and remember that a float is not strictly limited to 2 digits after the separator.
One solution is :
var firstHalf = Int(number)
var secondHalf = Int((number - firstHalf) * 100)
It's nasty but it'll do the right thing for your example (it will, however, fail when dealing with numbers that have more than two decimals of precision)
Alternatively, you could convert it into a string and then split that.
var stringified = NSString(format: "%.2f", number)
var parts = stringifed.componentsSeparatedByString(".")
Note that I'm explicitly calling the formatter here, to avoid unwanted behavior of standard float to string conversions.
Add the following extension:
extension Double {
func splitIntoParts(decimalPlaces: Int, round: Bool) -> (leftPart: Int, rightPart: Int) {
var number = self
if round {
//round to specified number of decimal places:
let divisor = pow(10.0, Double(decimalPlaces))
number = Darwin.round(self * divisor) / divisor
}
//convert to string and split on decimal point:
let parts = String(number).components(separatedBy: ".")
//extract left and right parts:
let leftPart = Int(parts[0]) ?? 0
let rightPart = Int(parts[1]) ?? 0
return(leftPart, rightPart)
}
Usage - Unrounded:
let number:Double = 95.99999999
let parts = number.splitIntoParts(decimalPlaces: 3, round: false)
print("LeftPart: \(parts.leftPart) RightPart: \(parts.rightPart)")
Outputs:
LeftPart: 95 RightPart: 999
Usage Rounded:
let number:Double = 95.199999999
let parts = number.splitIntoParts(decimalPlaces: 1, round: true)
Outputs:
LeftPart: 95 RightPart: 2
Actually, it's not possible to split a double by dot into two INTEGER numbers. All earlier offered solutions will produce a bug in nearly 10% of cases.
Here's why:
The breaking case will be numbers with decimal parts starting with one or more zeroes, for example: 1.05, 11.00698, etc.
Any decimal part that starts with one or more zeroes will have those zeroes discarded when converted to integers. The result of those conversions:
1.05 will become (1, 5)
11.00698 will become (11, 698)
An ugly and hard to find bug is guaranteed...
The only way to meaningfully split a decimal number is to convert it to (Int, Double). Below is a simple extension to Double that does that:
extension Double {
func splitAtDecimal() -> (Int, Double) {
let whole = Int(self)
let decimal = self - Darwin.floor(self)
return (whole, decimal)
}
}