Get a Decimal from a String - swift

I have a problem to get a Decimal here.
I have tried this code but the results is 9.0 , How can i get 0.9 ?
let distances = "0.9 mil"
let stratr = distances.characters.split{$0 == " "}.map(String.init)
for item in stratr {
let components = item.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
let part = components.joined(separator: "")
if let doubVal = Double(part) {
print("this is a number -> \(doubVal)")
}

You can separate the string by the space character and then initialize a Float using the first component.
let str = "0.9 mil"
let decimal = str.components(separatedBy: " ").first.flatMap { Float($0) }
print(decimal) // 0.9

The String struct provides an instance method that can be used to remove characters based on a given CharacterSet. In this case, you can use the letters and whitespaces character sets to isolate your decimal value and then create a Decimal from it.
let distances = "0.9 mil"
let decimal = Decimal(string: distances.trimmingCharacters(in: CharacterSet.letters.union(.whitespaces)))
if let decimal = decimal {
print(decimal) // Prints 0.9
}

extension String {
/// "0.9 mil" => "0.9"
var decimals: String {
return trimmingCharacters(in: CharacterSet.decimalDigits.inverted)
}
/// "0.9" => 0.9
var doubleValue: Double {
return Double(self) ?? 0
}
}
Usage:
let distance = "0.9 mil"
print(distance.decimals) // "0.9"
print(distance.decimals.doubleValue) // 0.9
print(distance.doubleValue) // 0 (because Double("0.9 mil") => nil)

Never mind i find the Answer
let distances = "0.9 mil"
let stratr = distances.characters.split{$0 == " "}.map(String.init)
for item in stratr {
let components = item.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
let part = components.joined(separator: ".")
if let doubVal = Double(part) {
print("this is a number -> \(doubVal)")
}
I think when i set Joined(separator : ".") it will joined the String with an "." as separator

Related

How to plus or minus a double value by step in swift?

Let's assume we have a double value 0.000025900,
Expect: Calling minus() returns 0.000025899 and plus() returns 0.000025901
What I've tried so far:
func plus(text: String) -> String {
let reminder = text.components(separatedBy: ".").last ?? "0"
let integer = text.components(separatedBy: ".").first ?? "0"
let str = (Int(reminder) ?? 0) + 1
return String(integer) + "." + String(str)
}
func minus(text: String) -> String {
let reminder = text.components(separatedBy: ".").last ?? "0"
let integer = text.components(separatedBy: ".").first ?? "0"
let str = max(0, (Int(reminder) ?? 0) - 1)
return String(integer) + "." + String(str)
}
These two functions only work well while there is no 0s between . and truncating part likes 0.25900.
Your code is good for the first part, which is separating the integer from the decimal part. To keep the decimal part properly formatted when the function returns, you need to store the number of characters in a variable, and use it to transform it back.
Here's the code for the plus() function:
func plus(text: String) -> String {
let remainder = text.components(separatedBy: ".").last ?? "0"
let integer = text.components(separatedBy: ".").first ?? "0"
// Store the number of characters of the remainder
let length = remainder.count
let newRemainder = (Double(remainder) ?? 0.0) + 1.0
// Turn the "integer remainder" into a Double using the number of characters to calculate the denominator
let result = (Double(integer) ?? 0.0) + newRemainder / pow(10, Double(length))
// Format the result using the number of characters as the precision
return String(result.formatted(.number.precision(.fractionLength(length))))
}
I still need to figure out the code for the minus() function... :)
There could be many solutions to this problem. Here is my solution. There could be some errors. I think it could help you with how to perform a minus function.
func plus(text: String) -> String {
guard text.contains(".") else {
// value = 2 return value should be 2.1
return text + ".1"
}
let intialValueAfterDecimalPoint = text.components(separatedBy: ".").last ?? "0"
// Make .00 to .100 / .9001 to .19001 if at some point only 0's after decimal point for making easy calculation
let modifiedIntialValueAfterDecimalPoint = "1" + intialValueAfterDecimalPoint
let intialIntegerValue = text.components(separatedBy: ".").first ?? "0"
let modifiedIntitialValueAfterDecimalPoint = Int(modifiedIntialValueAfterDecimalPoint) ?? 0
//Need to add carry value to initial interger value if there is a carry value in decimalValue addition
// For example .99 + 1 = 1.00
let firstIndexValueWithCarryValue = calculateCarryValue(text: String(modifiedIntitialValueAfterDecimalPoint + 1))
if firstIndexValueWithCarryValue == 2 {
let finalValueAfterDecimalPoint = String(modifiedIntitialValueAfterDecimalPoint + 1).dropFirst()
return String((Int(intialIntegerValue) ?? 0) + 1) + "." + finalValueAfterDecimalPoint
} else {
return String(intialIntegerValue) + "." + String(modifiedIntitialValueAfterDecimalPoint + 1).dropFirst()
}
}
func calculateCarryValue(text: String) -> Int {
let revString = String(text.reversed())
let carryValue = (Int(revString) ?? 0) % 10
return carryValue
}
func minus(text: String) -> String {
guard text.contains(".") else {
// value = 2 return value should be 1.9
let intValue = Int(text) ?? 0
if intValue == 0 {
return "-1"
} else {
return String(intValue - 1) + ".9"
}
}
let intialValueAfterDecimalPoint = text.components(separatedBy: ".").last ?? "0"
// Make .00 to .100 / .9001 to .19001 if at some point only 0's after decimal point for making easy calculation
let modifiedIntialValueAfterDecimalPoint = "1" + intialValueAfterDecimalPoint
let intialIntegerValue = text.components(separatedBy: ".").first ?? "0"
let modifiedIntitialValueAfterDecimalPoint = Int(modifiedIntialValueAfterDecimalPoint) ?? 0
//Need to minus carry value to initial interger value if there is a carry value in decimalValue subtraction
// For example 1.000 - 1 = 0.999
let firstIndexValueWithMinusCarryValue = String(modifiedIntitialValueAfterDecimalPoint - 1)
let negativeCarryValue = firstIndexValueWithMinusCarryValue.count < modifiedIntialValueAfterDecimalPoint.count ? 1 : 0
if negativeCarryValue == 1 {
let finalValueAfterDecimalPoint = String(modifiedIntitialValueAfterDecimalPoint - 1)
return String((Int(intialIntegerValue) ?? 0) - 1) + "." + finalValueAfterDecimalPoint
} else {
return String(intialIntegerValue) + "." + String(modifiedIntitialValueAfterDecimalPoint - 1).dropFirst()
}
}
print(plus(text: "1.00"))
print(plus(text: "0.9999"))
print(minus(text: "1.00"))
print(minus(text: "0.9999"))
Just use Decimal's nextUp and nextDown.

Convert string into currency number format, with, without or with 0 decimal places

I have a string that always converted into something like this where the user inputs a number and always starting in the decimal places,
So I have 0.01 -> 0.10 -> 1.00
but I don't want something like that, I want to convert only what the user has typed
here's my existing code that convert 100000 into 1,000.00
func convertme(string: String) -> String{
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = ""
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = string
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, string.count), withTemplate: "")
print("amountWithPrefix", amountWithPrefix)
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
expected result:
I want to to format the number on the string without adding additional data, I want to turn (100000. into 100,000.) (100000.0 into 100,000.0
I want my 100000 be converted into 100,000, and only going to have a decimal if the user inputed a decimal too, so when the user inputted 100000.00 it will be converted into 100,000.00.
PS. I have a regex there that accepts only number but not the decimal, how can I make it also accept decimal?
You can simply filter non digits or periods from the original string, try to coerce the resulting string to integer. If successful set the formatter maximum fraction digits to zero otherwise set the maximum fraction digits to 2 and coerce the string to double:
extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = ""
return formatter
}()
}
extension Numeric {
var currencyUS: String {
Formatter.currency.string(for: self) ?? ""
}
}
func convertme(string: String) -> String {
let string = string.filter("0123456789.".contains)
if let integer = Int(string) {
Formatter.currency.maximumFractionDigits = 0
return Formatter.currency.string(for: integer) ?? "0"
}
Formatter.currency.maximumFractionDigits = 2
return Double(string)?.currencyUS ?? "0"
}
convertme(string: "100000") // "100,000"
convertme(string: "100000.00") // "100,000.00"
edit/update:
"100,000." it is not a valid number format. You would need to manually insert your period at the end of the string.
func convertme(string: String) -> String {
var string = string.filter("0123456789.".contains)
// this makes sure there is only one period and keep only the last one in the string
while let firstIndex = string.firstIndex(of: "."),
let _ = string[firstIndex...].dropFirst().firstIndex(of: ".") {
string.remove(at: firstIndex)
}
// get the index of the period in your string
if let index = string.firstIndex(of: ".") {
// get the fraction digits count and set the number formatter appropriately
let fractionDigits = string[index...].dropFirst().count
Formatter.currency.minimumFractionDigits = fractionDigits
Formatter.currency.maximumFractionDigits = fractionDigits
// Number Formatter wont add a period at the end of the string if there is no fractional digits then you need to manually add it yourself
if fractionDigits == 0 {
return (Double(string)?.currencyUS ?? "0") + "."
}
} else {
// in case there is no period set the fraction digits to zero
Formatter.currency.minimumFractionDigits = 0
Formatter.currency.maximumFractionDigits = 0
}
return Double(string)?.currencyUS ?? "0"
}
Playground Testing:
convertme(string: "100000") // "100,000"
convertme(string: "100000.") // "100,000."
convertme(string: "100000.0") // "100,000.0"
convertme(string: "100000.00") // "100,000.00"
convertme(string: "100000.000") // "100,000.000"

Converting very large decimal numbers to hexadecimal in swift

We can use String Format specifier to convert an integer value or a long value to hexadecimal notation.
Int Example:
print(String(format:"%x", 1111))
//result:457
Long Example:
print(String(format:"%lx", 11111111111111))
//result:a1b01d4b1c7
But, what if we try to convert a very large decimal that is larger than uint64.max? //18446744073709551615
What is the right way to convert in this case?
One possible solution is to use NSDecimalNumber to hold the large value. But it doesn't have any built in way to convert the number into a string other than base 10.
The following is an extension to NSDecimalNumber that will convert the number into any base from 2 to 16. And it also includes a convenience init that takes a string in a given base.
extension NSDecimalNumber {
convenience init(string: String, base: Int) {
guard base >= 2 && base <= 16 else { fatalError("Invalid base") }
let digits = "0123456789ABCDEF"
let baseNum = NSDecimalNumber(value: base)
var res = NSDecimalNumber(value: 0)
for ch in string {
let index = digits.index(of: ch)!
let digit = digits.distance(from: digits.startIndex, to: index)
res = res.multiplying(by: baseNum).adding(NSDecimalNumber(value: digit))
}
self.init(decimal: res.decimalValue)
}
func toBase(_ base: Int) -> String {
guard base >= 2 && base <= 16 else { fatalError("Invalid base") }
// Support higher bases by added more digits
let digits = "0123456789ABCDEF"
let rounding = NSDecimalNumberHandler(roundingMode: .down, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)
let baseNum = NSDecimalNumber(value: base)
var res = ""
var val = self
while val.compare(0) == .orderedDescending {
let next = val.dividing(by: baseNum, withBehavior: rounding)
let round = next.multiplying(by: baseNum)
let diff = val.subtracting(round)
let digit = diff.intValue
let index = digits.index(digits.startIndex, offsetBy: digit)
res.insert(digits[index], at: res.startIndex)
val = next
}
return res
}
}
Test:
let bigNum = NSDecimalNumber(string: "18446744073709551615")
print(bigNum.toBase(16))
print(bigNum.toBase(10)) // or just print(bigNum)
print(NSDecimalNumber(string: "B7", base: 16))
print(NSDecimalNumber(string: NSDecimalNumber(string: "18446744073709551615").toBase(16), base: 16))
Output:
FFFFFFFFFFFFFFFF
18446744073709551615
183
18446744073709551615

In Swift, how to modify a character in string with subscript?

Like in C, we can simply do
str[i] = str[j]
But how to write the similar logic in swift?
Here is my code, but got error:
Cannot assign through subscript: subscript is get-only
let indexI = targetString.index(targetString.startIndex, offsetBy: i)
let indexJ = targetString.index(targetString.startIndex, offsetBy: j)
targetString[indexI] = targetString[indexJ]
I know it may work by using this method, but it's too inconvenient
replaceSubrange(, with: )
In C, a string (char *) can be treated as an array of characters. In Swift, you can convert the String to an [Character], do the modifications you want, and then convert the [Character] back to String.
For example:
let str = "hello"
var strchars = Array(str)
strchars[0] = strchars[4]
let str2 = String(strchars)
print(str2) // "oello"
This might seem like a lot of work for a single modification, but if you are moving many characters this way, you only have to convert once each direction.
Reverse a String
Here's an example of a simple algorithm to reverse a string. By converting to an array of characters first, this algorithm is similar to the way you might do it in C:
let str = "abcdefg"
var strchars = Array(str)
var start = 0
var end = strchars.count - 1
while start < end {
let temp = strchars[start]
strchars[start] = strchars[end]
strchars[end] = temp
start += 1
end -= 1
}
let str2 = String(strchars)
print(str2) // "gfedcba"
Dealing with String with Swift is major pain in the a**. Unlike most languages I know that treat string as an array of characters, Swift treats strings as collection of extended grapheme clusters and the APIs to access them is really clumsy. Changes are coming in Swift 4 but that manifesto lost me about 10 paragraphs in.
Back to your question... you can replace the character like this:
var targetString = "Hello world"
let i = 0
let j = 1
let indexI = targetString.index(targetString.startIndex, offsetBy: i)
let indexJ = targetString.index(targetString.startIndex, offsetBy: j)
targetString.replaceSubrange(indexI...indexI, with: targetString[indexJ...indexJ])
print(targetString) // eello world
I was quite shocked as well by the fact that swift makes string indexing so damn complicated. For that reason, I have built some string extensions that enable you to retrieve and change parts of strings based on indices, closed ranges, and open ranges, PartialRangeFrom, PartialRangeThrough, and PartialRangeUpTo. You can download the repository I created here
You can also pass in negative numbers in order to access characters from the end backwards.
public extension String {
/**
Enables passing in negative indices to access characters
starting from the end and going backwards.
if num is negative, then it is added to the
length of the string to retrieve the true index.
*/
func negativeIndex(_ num: Int) -> Int {
return num < 0 ? num + self.count : num
}
func strOpenRange(index i: Int) -> Range<String.Index> {
let j = negativeIndex(i)
return strOpenRange(j..<(j + 1), checkNegative: false)
}
func strOpenRange(
_ range: Range<Int>, checkNegative: Bool = true
) -> Range<String.Index> {
var lower = range.lowerBound
var upper = range.upperBound
if checkNegative {
lower = negativeIndex(lower)
upper = negativeIndex(upper)
}
let idx1 = index(self.startIndex, offsetBy: lower)
let idx2 = index(self.startIndex, offsetBy: upper)
return idx1..<idx2
}
func strClosedRange(
_ range: CountableClosedRange<Int>, checkNegative: Bool = true
) -> ClosedRange<String.Index> {
var lower = range.lowerBound
var upper = range.upperBound
if checkNegative {
lower = negativeIndex(lower)
upper = negativeIndex(upper)
}
let start = self.index(self.startIndex, offsetBy: lower)
let end = self.index(start, offsetBy: upper - lower)
return start...end
}
// MARK: - Subscripts
/**
Gets and sets a character at a given index.
Negative indices are added to the length so that
characters can be accessed from the end backwards
Usage: `string[n]`
*/
subscript(_ i: Int) -> String {
get {
return String(self[strOpenRange(index: i)])
}
set {
let range = strOpenRange(index: i)
replaceSubrange(range, with: newValue)
}
}
/**
Gets and sets characters in an open range.
Supports negative indexing.
Usage: `string[n..<n]`
*/
subscript(_ r: Range<Int>) -> String {
get {
return String(self[strOpenRange(r)])
}
set {
replaceSubrange(strOpenRange(r), with: newValue)
}
}
/**
Gets and sets characters in a closed range.
Supports negative indexing
Usage: `string[n...n]`
*/
subscript(_ r: CountableClosedRange<Int>) -> String {
get {
return String(self[strClosedRange(r)])
}
set {
replaceSubrange(strClosedRange(r), with: newValue)
}
}
/// `string[n...]`. See PartialRangeFrom
subscript(r: PartialRangeFrom<Int>) -> String {
get {
return String(self[strOpenRange(r.lowerBound..<self.count)])
}
set {
replaceSubrange(strOpenRange(r.lowerBound..<self.count), with: newValue)
}
}
/// `string[...n]`. See PartialRangeThrough
subscript(r: PartialRangeThrough<Int>) -> String {
get {
let upper = negativeIndex(r.upperBound)
return String(self[strClosedRange(0...upper, checkNegative: false)])
}
set {
let upper = negativeIndex(r.upperBound)
replaceSubrange(
strClosedRange(0...upper, checkNegative: false), with: newValue
)
}
}
/// `string[...<n]`. See PartialRangeUpTo
subscript(r: PartialRangeUpTo<Int>) -> String {
get {
let upper = negativeIndex(r.upperBound)
return String(self[strOpenRange(0..<upper, checkNegative: false)])
}
set {
let upper = negativeIndex(r.upperBound)
replaceSubrange(
strOpenRange(0..<upper, checkNegative: false), with: newValue
)
}
}
}
Usage:
let text = "012345"
print(text[2]) // "2"
print(text[-1] // "5"
print(text[1...3]) // "123"
print(text[2..<3]) // "2"
print(text[3...]) // "345"
print(text[...3]) // "0123"
print(text[..<3]) // "012"
print(text[(-3)...] // "345"
print(text[...(-2)] // "01234"
All of the above works with assignment as well. All subscripts have getters and setters.
a new extension added,
since String conforms to BidirectionalCollection Protocol
extension String{
subscript(at i: Int) -> String? {
get {
if i < count{
let idx = index(startIndex, offsetBy: i)
return String(self[idx])
}
else{
return nil
}
}
set {
if i < count{
let idx = index(startIndex, offsetBy: i)
remove(at: idx)
if let new = newValue, let first = new.first{
insert(first, at: idx)
}
}
}
}
}
call like this:
var str = "fighter"
str[at: 2] = "6"

NSNumberFormatter : Show 'k' instead of ',000' in large numbers?

I'd like to change my large numbers from 100,000 to $100K if this is possible.
This is what I have so far:
let valueFormatter = NSNumberFormatter()
valueFormatter.locale = NSLocale.currentLocale()
valueFormatter.numberStyle = .CurrencyStyle
valueFormatter.maximumFractionDigits = 0
My Question
Using NSNumberFormatter, how can I output $100K rather than $100,000?
My original question:
This is what I have so far:
self.lineChartView.leftAxis.valueFormatter = NSNumberFormatter()
self.lineChartView.leftAxis.valueFormatter?.locale = NSLocale.currentLocale()
self.lineChartView.leftAxis.valueFormatter?.numberStyle = .CurrencyStyle
self.lineChartView.leftAxis.valueFormatter?.maximumFractionDigits = 0
Which Translates to:
let valueFormatter = NSNumberFormatter()
valueFormatter.locale = NSLocale.currentLocale()
valueFormatter.numberStyle = .CurrencyStyle
valueFormatter.maximumFractionDigits = 0
My output looks like this:
My Question
Using NSNumberFormatter, how can I output $100K rather than $100,000?
update:
I wanted to provide context as to whats going on, watch comments.
func setDollarsData(months: [String], range: Double) {
var dataSets: [LineChartDataSet] = [LineChartDataSet]()
var yVals: [ChartDataEntry] = [ChartDataEntry]()
for var i = 0; i < months.count; i++ {
// I'm adding my values here in value:, value takes a Double
yVals.append(ChartDataEntry(value: county[userFavs[0]]![i], xIndex: i))
}
let set1: LineChartDataSet = LineChartDataSet(yVals: yVals, label: self.userFavs[0])
set1.axisDependency = .Left
set1.setColor(UIColor.redColor().colorWithAlphaComponent(0.5))
set1.setCircleColor(UIColor.redColor())
set1.lineWidth = 2.0
set1.circleRadius = 6.0
set1.fillAlpha = 65 / 255.0
dataSets.append(set1)
let data: LineChartData = LineChartData(xVals: months, dataSets: dataSets)
data.setValueTextColor(UIColor.whiteColor())
// this is where I set the number formatter
self.lineChartView.gridBackgroundColor = UIColor.darkGrayColor()
self.lineChartView.leftAxis.startAtZeroEnabled = false
self.lineChartView.leftAxis.valueFormatter = NSNumberFormatter()
self.lineChartView.leftAxis.valueFormatter?.locale = NSLocale.currentLocale()
self.lineChartView.leftAxis.valueFormatter?.numberStyle = .CurrencyStyle
self.lineChartView.leftAxis.valueFormatter?.maximumFractionDigits = 0
// set it to the chart // END OF THE LINE
self.lineChartView.data = data // outputs to my chart
}
As you can see, once I dump the numbers into yVals, I lose access to them so those extensions will only work if I hack into the framework.
edit/update
Swift 3 or later
extension FloatingPoint {
var kFormatted: String {
return String(format: self >= 1000 ? "$%.0fK" : "$%.0f", (self >= 1000 ? self/1000 : self) as! CVarArg )
}
}
The you can use it like this to format your output:
10.0.kFormatted // "$10"
100.0.kFormatted // "$100"
1000.0.kFormatted // "$1K"
10000.0.kFormatted // "$10K"
162000.0.kFormatted // "$162K"
153000.0.kFormatted // "$153K"
144000.0.kFormatted // "$144K"
135000.0.kFormatted // "$135K"
126000.0.kFormatted // "$126K"
I've bumped into the same issue and solved it by implementing a custom formatter. Just started coding in Swift, so the code might not be the most idiomatic.
open class KNumberFormatter : NumberFormatter {
override open func string(for obj: Any?) -> String? {
if let num = obj as? NSNumber {
let suffixes = ["", "k", "M", "B"]
var idx = 0
var d = num.doubleValue
while idx < 4 && abs(d) >= 1000.0 {
d /= 1000.0
idx += 1
}
var currencyCode = ""
if self.currencySymbol != nil {
currencyCode = self.currencySymbol!
}
let numStr = String(format: "%.1f", d)
return currencyCode + numStr + suffixes[idx]
}
return nil
}
}
I think you can add an extension to NSNumberFormatter. Try the following, I didn't test it so let me know in the comment if it needs to be edited
extension NSNumberFormatter {
func dividedByK(number: Int)->String{
if (number % 1000) == 0 {
let numberK = Int(number / 1000)
return "\(numberK)K"
}
return "\(number)"
}
}