Double String formatting madness - swift

I have the following in playground:
let a:CGFloat = 1.23
let b:Double = 3.45
let c:Float = 6.78
String(format: "(%2.2f|%2.2f|%2.2f)",a,b,c) // prints "(3.45|6.78|0.00)"
let d = NSMakePoint(9.87,6.54)
String(format: "(%2.2f|%2.2f)",d.x,d.y) // prints "(0.00|0.00)"
So why is c:Float rendered as 0.00. I possibly need something else than f (Apples docu on this formatting function is - to be polite - quite thin with a limes going to zero).
BUT: Why is the CGFloat in the first place rendered correctly while the two CGFloats inside the NSPoint get rendered as 0.00?
And no: it's not a duplicate of Precision String Format Specifier In Swift and its pendant.
P.S.
String(format: "(%2.2f|%2.2f)",Double(d.x),d.y) // prints "(9.87|0.00)"
which is a work around but no explanation.
And a PPS: Isn't %2.2f supposed to print " 9.87" instead of "9.87" (2 places for leading digits? It seems to ignore the number. Specifying %02.2f also prints "9.87" rather than "09.87"

CGFloat isn't a float or a double, it is its own struct. If you want the double or float value of a CGFloat (which is dependent on the architecture being 32 or 64 but, then you can access it with a.native. In other words try:
String(format: "(%2.2f|%2.2f|%2.2f)",a.native,b,c)
You'd see similar behavior if you tried to pass other non-float or non-double arguments to the %f formatter. For example:
var str = "Hello, playground"
String(format: "%2.2f|%2.2f|%2.2f", arguments: [str,b,c])
would result in "3.45|6.78|0.00". It appears to be looking for another float in your arguments to satisfy the last %f, and defaults to 0.00
As for the PPS. %2.2F is two decimal place and at least 2 total digits. If you wanted two digits minimum before the decimal, you'd want %5.2f. 5 because the decimal itself takes a place.

Related

String Format Specifiers : rounding rule used for decimal values

I am using String(format:) to convert a Float. I thought the number would be rounded.
Sometimes it is.
String(format: "%.02f", 1.455) //"1.46"
Sometimes not.
String(format: "%.02f", 1.555) //"1.55"
String(round(1.555 * 100) / 100.0) //"1.56"
I guess 1.55 cannot be represented exactly as binary. And that it becomes something like 1.549999XXXX
But NumberFormatter doesn't seem to cause the same problem... Why? Should it be preferred over String(format:)?
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
if let string = formatter.string(for: 1.555) {
print(string) // 1.56
}
Reference to the problem (to use String (format :) to round a decimal number) can be found in the answers (or more often comments) to these questions: Rounding a double value to x number of decimal places in swift and How to format a Double into Currency - Swift 3. But the problem it covers (math with FloatingPoint) has been dealt with many times on SO (for all languages).
String(format:) does not have the function of rounding a decimal number (even if it is unfortunately proposed in some answers) but of formatting it (as its name suggests). This formatting sometimes causes a rounding. That is true. But we have to keep in mind a problem that the number 1.555 is... not worth 1.555.
In Swift, Double and Float, that conform to the FloatingPoint protocol respect the IEEE 754 specification. However, some values ​​cannot be exactly represented by the IEEE 754 standard.
In the same way that you can't represent a third exactly in a (finite) decimal expansion, there are lots of numbers which look simple in decimal, but which have long or infinite expansions in a binary expansion." (source)
To be convinced of this, we can use The Float Converter to convert between the decimal representation of numbers (like "1.02") and the binary format used by all modern CPUs (IEEE 754 floating point). For 1.555, the value actually stored in float is 1.55499994754791259765625
So the problem does not come from String (format :). For example, we can try another way to round to the thousandth and we find the same problem. :
round (8.45 * pow (10.0, 3.0)) / pow (10.0, 3.0)
// 8.449999999999999
That is how it is : "Binary floating point arithmetic is fine so long as you know what's going on and don't expect values ​​to be exactly the decimal ones you put in your program".
So the real question is : is this really a problem for you to use ? It depends on the app. Generally if we convert a number into a String by limiting its precision (by rounding), it is because we consider that this precision is not useful to the user. If this is the kind of data we're talking about, then it's okay to use a FloatingPoint.
However, to format it it may be more relevant to use a NumberFormatter. Not necessarily for its rounding algorithm, but rather because it allows you to locate the format :
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.locale = Locale(identifier: "fr_FR")
formatter.string(for: 1.55)!
// 1,55
formatter.locale = Locale(identifier: "en_US")
formatter.string(for: 1.55)!
// 1.55
Conversely, if we are in a case where precision matters, we must abandon Double / Float and use Decimal. Still to keep our rounding example, we can use this extension (which may be the best answer to the question "Rounding a double value to x number of decimal places in swift ") :
extension Double {
func roundedDecimal(to scale: Int = 0, mode: NSDecimalNumber.RoundingMode = .plain) -> Decimal {
var decimalValue = Decimal(self)
var result = Decimal()
NSDecimalRound(&result, &decimalValue, scale, mode)
return result
}
}
1.555.roundedDecimal(to: 2)
// 1.56

How do you get integer values in an NSPoint to display as integer rather than as decimal

How do you get integer values in an NSPoint to display as integer rather than as decimal?
let temp:NSPoint = NSMakePoint(5,7)
print(temp.x) . // displays 5.0
I only want it to display 5, not 5.0
You ask:
How do you get integer values in an NSPoint to display as integer rather than as decimal.
This question may be based upon a false premise. The parameters you supply to NSMakePoint(_:_:) are actually CGFloat (as are the the underlying properties, x and y). So although you supplied integers, but Swift stores them as CGFloat for x and y, regardless.
So, the question is “How do you display the CGFloat properties of a NSPoint without any fractional digits?”
Certainly, as others have suggested, you can create an integer from the CGFloat properties with Int(point.x), etc., but I might suggest that you really might want want to just display these CGFloat without decimal places. There are a bunch of ways of doing this.
Consider:
let point = NSPoint(x: 5000, y: 7000)
You can use NSMakePoint, too, but this is a little more natural in Swift.
Then to display this without decimal places, you have a few options:
In macOS, NSStringFromPoint will generate the full CGPoint without decimal places:
print(NSStringFromPoint(point)) // "{5000, 7000}"
You can use String(format:_:), using a format string that explicitly specifies no decimal places:
print(String(format: "%.0f", point.x)) // "5000"
You can use NumberFormatter and specify the minimumFractionDigits:
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
formatter.numberStyle = .decimal
print(formatter.string(for: point.x)!) // "5,000"
Needless to say, you can save that formatter and you can use it repeatedly, as needed, if you want to keep your code from getting too verbose.
If you’re just logging this to the console, you can use Unified Logging with import os.log and then do:
os_log("%.0f", point.x) // "5000"

Round a double down to one decimal place (dropping decimal places)

I want to round a double down to 1 decimal place. For example if I have a double let val = 3.1915 I want to round this down to 3.1. Normal rounding functions will round it to 3.2 but I want to basically just drop the remaining decimal places. What is the best way to do this? Is there a native function for this? I know this is pretty straight forward to do but I want to know what the best way to do this would be where I am not using any kind of workaround or bad practices. This is not a duplicate of other rounding questions because I am not asking about about rounding, I am asking how to drop decimal places.
Similarly, if the value was 3.1215, it would also round to 3.1
Use the function trunc() (which stands for truncate) which will chop away the decimal portion without rounding. Specifically, multiply the Double value by 10, truncate it, then divide by 10 again. Then, to display using 1 decimal place, use String(format:):
let aDouble = 1.15
let truncated = trunc(aDouble * 10) / 10
let string = String(format: "%.1f", truncated
print(string)
(displays "1.1")
or, to process an entire array of sample values:
let floats = stride(from: 1.099, to: 2.0, by: 0.1)
let truncs = floats
.map { trunc($0 * 10) / 10 }
.map { String(format: "%.1f", $0) }
let beforeAndAfter = zip(floats, truncs)
.map { (float: $0.0, truncString: $0.1)}
beforeAndAfter.forEach { print(String(format: "%.3f truncated to 1 place is %#", $0.0, $0.1)) }
Outputs:
1.099 truncated to 1 place is 1.0
1.199 truncated to 1 place is 1.1
1.299 truncated to 1 place is 1.2
1.399 truncated to 1 place is 1.3
1.499 truncated to 1 place is 1.4
1.599 truncated to 1 place is 1.5
1.699 truncated to 1 place is 1.6
1.799 truncated to 1 place is 1.7
1.899 truncated to 1 place is 1.8
1.999 truncated to 1 place is 1.9
By your example I assume you meant you want to Truncate, if so using multiply and casting into Int then Dividing and casting back into Float/Double will do.
Example: 3.1915 -> 3.1
var val = 3.1915
let intVal:Int = Int(val*10)
val = Float(intVal)/10.0
print(val) //3.1
If you want more decimal places simply multiply and divide by 100 or 1000 instead.
Then if for any reason you want to use the round() function there is a overloaded variant that accepts a FloatingPointRoundingRule it will work like:
var val = 3.1915
val*=10 //Determine decimal places
val.round(FloatingPoint.towardZero) // .down is also available which differs in rounding negative numbers.
val*=0.1 //This is a divide by 10
print(val) //3.1
In practical usage I'd suggest making an extension or global function instead of writing this chunk every time. It would look something like:
extension Float {
func trunc(_ decimal:Int) {
var temp = self
let multiplier = powf(10,decimal) //pow() for Double
temp = Float(Int(temp*multiplier))/multiplier //This is actually the first example put into one line
return temp
}
}
And used:
var val = 3.1915
print(val.trunc(1)) //3.1

How do I convert a fractional decimal to a whole number in Swift

What is the easiest way to convert the fractional part of a float decimal (the part on the right) to a whole number integer.
For example:
0.25 converts to 25
0.09 converts to 9
0.90 converts to 90
I've tried several ways, including converting the float to a string and extracting the fraction, but for some reason it leaves off any trailing zeros. For example 0.90 would convert to a string as 0.9.
Here is an example:
let a = 0.90
let fractionalPart = a.truncatingRemainder(dividingBy: 1.0)
let modifiedFractionalPart = Int(fractionalPart * 100.0)
let string = String(modifiedFractionalPart)
// prints 90
If you aren't allowed to multiply by 100.0, meaning you don't actually have to limit your fractional part to two decimal places, rather you need to have the whole part after the . then use the following:
let a = 0.09017
let fractionalPart = String(a).components(separatedBy: ".")[1] // "09017"
Then if you have to convert it to an Int just do:
let fractionalPartInt = Int(fractionalPart) // 09017
Apple's Foundation Framework provides a way to do this:
let numberFormatter = NumberFormatter()
numberFormatter.multiplier = 100
print(numberFormatter.string(from: 0.25))
Specific documentation for NumberFormatter is also available
The advantage of using the NumberFormatter is that it is:
More modular -- if you need to change the conversion factor, just change the multiplier
More expressive -- having self-documenting code is extremely useful when viewing an old project

Round currency closest to five

I'd like to round my values to the closest of 5 cent for example:
5.31 -> 5.30
5.35 -> 5.35
5.33 -> 5.35
5.38 -> 5.40
Currently I'm doing it by getting the decimal values using:
let numbers = 5.33
let decimal = (numbers - rint(numbers)) * 100
let rounded = rint(numbers) + (5 * round(decimal / 5)) / 100
// This results in 5.35
I was wondering if there's a better method with fewer steps because sometimes numbers - rint(numbers) is giving me a weird result like:
let numbers = 12.12
let decimal = (numbers - rint(numbers)) * 100
// This results in 11.9999999999999
Turns out..it's really simple
let x: Float = 1.03 //or whatever value, you can also use the Double type
let y = round(x * 20) / 20
It's really better to stay away from floating-point for this kind of thing, but you can probably improve the accuracy a little with this:
import Foundation
func roundToFive(n: Double) -> Double {
let f = floor(n)
return f + round((n-f) * 20) / 20
}
roundToFive(12.12) // 12.1
I will use round function and NSNumberFormatter also but slightly different algorithm
I was thinking about using % but I changed it to /
let formatter = NSNumberFormatter()
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
//5.30
formatter.stringFromNumber(round(5.31/0.05)*0.05)
//5.35
formatter.stringFromNumber(round(5.35/0.05)*0.05)
//5.35
formatter.stringFromNumber(round(5.33/0.05)*0.05)
//5.40
formatter.stringFromNumber(round(5.38/0.05)*0.05)
//12.15
formatter.stringFromNumber(round(12.13/0.05)*0.05)
Depending on how you are storing your currency data, I would recommend using a dictionary or an array to look up the original cents value, and return a pre-computed result. There's no reason to do the calculations at all, since you know that 0 <= cents < 100.
If your currency is a string input, just chop off the last couple of digits and do a dictionary lookup.
round_cents = [ ... "12":"10", "13":"15", ... ]
If your currency is a floating point value, well, you have already discovered the joys of trying to do that. You should change it.
If your currency is a data type, or a fixed point integer, just get the cents part out and do an array lookup.
...
round_cents[12] = 10
round_cents[13] = 15
...
In either case, you would then do:
new_cents = round_cents[old_cents]
and be done with it.