String number with limited precision fraction digits in Swift - swift

What would be the fastest way to convert a String number "1234.5678" to an NSNumber with precision -> 1234.56 and back to String "1234.56".
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 2
numberFormatter.string(from: numberFormatter.number(from: "1234.534234")!)
This code does not look that beautiful. Any ideas?

You can use the new formatted method and specify the number of precision fraction length to two:
let decimal = Decimal(
sign: .plus,
exponent: -4,
significand: 12345678
) // 1234.5678
decimal.formatted(.number.precision(.fractionLength(2))) // "1,234.57"
decimal.formatted(.number.grouping(.never).precision(.fractionLength(2))) // "1234.57"
decimal.formatted(.number.grouping(.never).rounded(rule: .towardZero).precision(.fractionLength(2))) // "1234.56"

Alternatively if you are only interested in the string regardless of any numeric modification like rounding you can strip the unwanted characters with Regular Expression
let string = "1234.5678"
let trimmedString = string.replacingOccurrences(of: "(\\d+\\.\\d{2})\\d.",
with: "$1",
options: .regularExpression)

Related

NumberFormatter return the wrong number

I test the NumberFormatter to get the number from priceWithCurrency.
If price bigger than $70 NumberFormatter converted the wrong number.
lazy var currencyFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "USD"
return formatter
}()
let price = "$71.9"
currencyFormatter.number(price)
//71.90000000000001
If you do the same thing with 71.8, it will work. This is just because 71.9 can't be represented precisely in floating point numbers ( due to the finitude of bits )
Use integers ( price in cents ) or decimal numbers is the only issue to deal correctly with prices. Check the Decimal and NSDecimalNumber classes to see what you can do with this.
It is specially recommended if you do some computations on prices, ( If you pay 10$ with two friends in cash, two will pay 3.33, and one will pay 3.34 - so depending of your context, divide by 3 might not be enough)
let number = currencyFormatter.number(from: price) ?? NSNumber(value: 0)
var value = number.decimalValue
var roundedValue: Decimal = 0
// Right way: ( choose your precisions - 3 decimals here):
NSDecimalRound(&roundedValue, &value, 3, .bankers)
print(number)
print(value)
print(roundedValue)
71.90000000000001
71.90000000000001
71.9
If you just need to log your prices, simply use Int(price*100)/100
It will do the job
If you need more... Good luck :)
Edit
Following the excellent remark of #Nicholas Rees, I add this variation:
currencyFormatter.generatesDecimalNumbers = true
let number = (currencyFormatter.number(from: price) as? NSDecimalNumber) ?? NSDecimalNumber(value: 0)
var value = number.decimalValue
var roundedValue: Decimal = 0
// Right way: ( choose your precisions - 3 decimals here):
NSDecimalRound(&roundedValue, &value, 3, .bankers)
print(number)
print(value)
print(roundedValue)
There, the result in the same when logged, but I suppose the internal format of the 'value' is correct
Another approach is to remove currency and create decimal from string:
print(Decimal(string: "71.9") ?? 0)
71.9

How to convert NSString to float without rounding it?

I have tried the following but when I convert the string it is automatically rounded.
let str = "10,60"
let str2 = (str as NSString).floatValue //prints "10.0"
//What I would like to do
let str2 = (str as NSSTring).floatValueNotRounded //prints "10,60"
.floatValue does not handle local formats and your number uses a comma as the decimal point - the the parse just stops at the comma and you get 10. Use either NumberFormatter or Scanner to parse localised numbers. E.g.:
let formatter = NumberFormatter()
let val = formatter.number(from: str)
should work provided your locale uses the comma as the decimal point. If you are in one locale and wish to parse numbers written according to another you can set locale property of the formatter.

Find number in string and return location and length

let myStr = "I have 4.34 apples."
I need the location range and the length, because I'm using NSRange(location:, length:) to bold the number 4.34
extension String{
func findNumbersAndBoldThem()->NSAttributedString{
//the code
}
}
My suggestion is also based on regular expression but there is a more convenient way to get NSRange from Range<String.Index>
let myStr = "I have 4.34 apples."
if let range = myStr.range(of: "\\d+\\.\\d+", options: .regularExpression) {
let nsRange = NSRange(range, in: myStr)
print(nsRange)
}
If you want to detect integer and floating point values use the pattern
"\\d+(\\.\\d+)?"
The parentheses and the trailing question mark indicate that the decimal point and the fractional digits are optional.

How to convert Exponential Value to Decimal Value

let balance = "2.477854178281608e-06"
// I have to convert this exponential value in Decimal
/* Already tried the below-mentioned solution, but not working */
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let finalNumber = numberFormatter.number(from: balance)
print(finalNumber!)
Value is printing "2.477854178281608e-06\n"
Any help will be appreciated.
let balance = "2.477854178281608e-06"
// I have to convert this exponential value in Decimal
This is not an exponential value. This is a string that represents a number using exponential format. You seem to want a string representing the same number in a different format. The important thing here is that neither string is "the value." The value is the same regardless of representation (or approximately the same if the representation is limited).
So first you need the value represented by the string. To do that, convert it to a Double.
let value = Double(balance)!
Now, you say you want to convert that to a string in decimal format (I assume you mean 0.000...). So you need a formatter:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 20
let string = numberFormatter.string(for: value)!
print(string) // 0.00000247785417828161
You'll note that this value is slightly different than the previous value. That's because there are rounding errors when dealing with values this small.
If all of these base-10 digits are important, you can work with the Decimal type rather than Double. This avoids decimal/binary rounding, but is less convenient and slower for some kinds of math. If this is a type of currency that is expressed in base-10 units (which is basically all of them), you always want to work with Decimal and never with Double.
let balance = "2.477854178281608e-06"
let value = Decimal(string: balance)!
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 21
let string = numberFormatter.string(for: value)!
print(string) // 0.000002477854178281608

No scientific notation and rounding with double

I want to round a number to 6 places and I don't want it to be displayed using scientific notation. I am using this code to round the decimal to 6 places but if the value is really small, it is still displaying it using scientific notation. I know that I can use the number formatter (displayed below as well) to remove the scientific notation but if I do that then it is returns a string so I cannot round a string to a certain number of decimal places. If I do the rounding first, that still doesn't work in all scenarios. It still shows some numbers without rounding. What is the best way to achieve this? It should work in all different scenarios where numbers are infinitely long or repeating
extension Double {
// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
numberFormatter.number(from:"")
I tried to use this code
let currentValue = 1/2.3344 //answer is 0.42837559972584...
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
numberFormatter.maximumFractionDigits = 6
guard let finalNum = numberFormatter.number(from: String(describing: currentValue)) else {return nil}
text = String (describing: finalNum)
I want it to display 0.428375. The value rounded to 6 decimal places.
When the current value is this: let currentValue = 1/233442 which is 0.00000428371... I want it to display 0.000004. The decimal rounded to 6 decimal places and not in scientific notation.
When the current value is this: let currentValue = 1/2334429 I want it to display 0 because that is the value rounded to 6 decimal places.
The number(from: someString) method converts a string to a number,
you want it the other way around:
let currentEntry = 1/2.3344
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 6
guard let text = numberFormatter.string(for: currentEntry) else {
// ...
}
print(text) // 0.428376
Note that your String(describing:) conversions only hide the problem.
You really should avoid String(describing:) (even if the compiler suggests
it as a Fix-it!), it almost never does what you need.