Help requested with numbers of more than ten digits into strings.
BlitzMax Forums/BlitzMax Beginners Area/Help requested with numbers of more than ten digits into strings.
| ||
Tried to make a function to take an int number and return a string with added commas to delimit thousands, millions, etc. But, something goes wrong with numbers of more than ten characters.. Explanation kindly appreciated! Problem is not changed by switching to long or double. I expect there is a simpler solution for the function, but now i would like to know what is going on with the numbers. Thanks. ------------------------------------------------------------------- Function IntToStringDelimeted:String(_value:Int) Local s:String = (_value) Local out:String Local leng:Int = s.length For Local i:Int = 0 To s.length-1 Local ai:Int = leng-i-1 Local c:String = Chr(s[ai]) out = c + out If (i+1) Mod 3 = 0 Then out = "," + out EndIf Next Return out EndFunction Local test_array:Int[10] test_array[0] = 1234567 test_array[1] = 12345678 test_array[2] = 123456789 test_array[3] = 1234567890 test_array[4] = 12345678900 test_array[5] = 123456789000 test_array[6] = 1000000000 test_array[7] = 10000000000 For Local i = 0 To 7 Print test_array[i] + " > to delimited string: " + IntToStringDelimeted(test_array[i]) -------------------------------------------------------- 1234567 > to delimited string: 1,234,567 12345678 > to delimited string: 12,345,678 123456789 > to delimited string: ,123,456,789 1234567890 > to delimited string: 1,234,567,890 -539222988 > to delimited string: -,539,222,988 -1097262584 > to delimited string: -1,097,262,584 1000000000 > to delimited string: 1,000,000,000 1410065408 > to delimited string: 1,410,065,408 Process complete |
| ||
The problem is: 123456789 > to delimited string: ,123,456,789 -> your loop is also ","-ing when reaching the most left char. so you should stop 1 char _before_ the most left one, and just prepend this one at the end -539222988 > to delimited string: -,539,222,988 -> here the problem is the "-" which is not a number but is handled as if it was one In your case you could also try to: - calc how many times pairs of 3 could be build ( pairs = int(string(abs(i)).length / 3)) - loop from 0 to pairs*3-1 and begin from the right The "abs" is there to ignore the "-" (you could also do it in an "if i<0" to avoid the abs() call)) Alternatively: instead of "/ 3" you could also do a "mod 3" to see how many chars are in front of the "pairs". In both cases you then need to do a "pairs = pairs - 1" if the length equals to a multiple of 3. The "mod"-approach already does this check (if "length mod 3" results in 0, you have to subtract 3 from your loops "max" value). BTW: this is how I did that function (looks like if I could simplify it too): Function dottedValue:String(value:Double, thousandsDelimiter:String=".", decimalDelimiter:String=",", digitsAfterDecimalPoint:int = -1) 'is there a "minus" in front ? Local addSign:String = "" If value < 0 Then addSign="-" Local stringValue:String = String(Abs(value)) 'find out amount of digits before decimal point Local length:Int = String(Abs(Long(value))).length 'add 2 to length, as this contains the "." delimiter Local fractionalValue:String = Mid(stringValue, length+2, -1) Local decimalValue:String = Left(stringValue, length) Local result:String = "" 'do we have a fractionalValue <> ".000" ? If Long(fractionalValue) > 0 if digitsAfterDecimalPoint > 0 'not rounded, just truncated fractionalValue = Left(fractionalValue, digitsAfterDecimalPoint) result :+ decimalDelimiter + fractionalValue endif endif For Local i:Int = decimalValue.length-1 To 0 Step -1 result = Chr(decimalValue[i]) + result 'every 3rd char, but not if the last one (avoid 100 -> .100) If (decimalValue.length-i) Mod 3 = 0 And i > 0 result = thousandsDelimiter + result EndIf Next Return addSign+result End Function Source: https://github.com/GWRon/TVTower/blob/master/source/basefunctions.bmx bye Ron |
| ||
Hi, I'm guessing number 2147483648 (2^31) is the hard limit for 32-bit application. Strings of course have no limit. -Henri |
| ||
I'd skip String conversions and do it yourself... Function IntToStringDelimeted2:String(_value:Int) Local neg:Int = 0 If _value < 0 Then neg = 1 End If Local out:String Local i:Int = 0 While _value Local digit:Int = Abs(_value Mod 10) If i And i Mod 3 = 0 Then out = "," + out End If out = digit + out _value :/ 10 i :+ 1 Wend If neg Then out = "-" + out End If Return out EndFunction |
| ||
Okey, by default it seems Blitzmax is using short version of long variable (32-bit), so in order to utilize 64-bit version you would need to explicitly define value as :long. 64 bit = 9223372036854775808 = 2^63 Local l:Long = 9223372036854775806:Long DebugLog l l = 9223372036854775807:Long DebugLog l l = 9223372036854775808:Long DebugLog l -Henri |
| ||
I shortened Bruceys code a bit (shaping off a handful of percents ):Function IntToStringDelimeted3:String(_value:Int) Local neg:Int = (_value < 0) Local out:String Local i:Int = 0 While _value Local digit:Int = Abs(_value Mod 10) If i And i Mod 3 = 0 Then out = digit + "," + out Else out = digit + out End If _value :/ 10 i :+ 1 Wend If neg Then Return "-" + out Else Return out End If EndFunction And I redid my code a bit - so it only processes decimals when requested. My code is still ~10% slower than Brucey's (and surely does more GC work) but his approach could be implemented too. Function dottedValue2:String(value:Double, thousandsDelimiter:String=".", decimalDelimiter:String=",", digitsAfterDecimalPoint:int = -1) 'is there a "minus" in front ? Local addSign:Int = value < 0 Local result:String Local decimalValue:string 'only process decimals when requested if digitsAfterDecimalPoint > 0 Local stringValues:String[] = String(Abs(value)).Split(".") Local decimalValue:string = stringValues[0] Local fractionalValue:String = "" if stringValues.length > 1 then fractionalValue = stringValues[1] 'do we even have a fractionalValue <> ".000" ? if Long(fractionalValue) > 0 'not rounded, just truncated fractionalValue = Left(fractionalValue, digitsAfterDecimalPoint) result :+ decimalDelimiter + fractionalValue endif else decimalValue = String(Abs(Long(value))) endif For Local i:Int = decimalValue.length-1 To 0 Step -1 result = Chr(decimalValue[i]) + result 'every 3rd char, but not if the last one (avoid 100 -> .100) If (decimalValue.length-i) Mod 3 = 0 And i > 0 result = thousandsDelimiter + result EndIf Next if addSign Return "-" + result else Return result endif End Function bye Ron |
| ||
My example was just to show an example of not converting to a String in the first instance. It certainly isn't efficient - as no BlitzMax String concatenation code is. |
| ||
How to do "string less" with decimals? Is there a better way than coverting to "long" ...continuing with your approach and to append the desired amount of decimals (value minus long-value ... with chances of 1.9999 to become 2.00001-2=0.00001)? Maybe one of you could come up with some efficient code/idea. Bye Ron |
| ||
Thanks everyone for responding. My original post was poorly worded. While the improved functions are greatly appreciated, it is only Henri's response that addresses what i really meant to ask about. Notice what happens to "test" 4, 5. The numbers are completely different to what the [i lacking the proper terminology] variable was defined as. But, as Henri wrote, using long and then also adding ":long" to the "variable definition" AFTER the number solves problem. See "test" 6 and 7. Function IntToStringDelimeted2:String(_value:Long) Local neg:Int = 0 If _value < 0 Then neg = 1 EndIf Local out:String Local i:Int = 0 While _value Local digit:Int = Abs(_value Mod 10) If i And i Mod 3 = 0 Then out = "," + out EndIf out = digit + out _value :/ 10 i :+ 1 Wend If neg Then out = "-" + out EndIf Return out EndFunction Local test_array:Long[10] test_array[0] = 1234567 test_array[1] = 12345678 test_array[2] = 123456789 test_array[3] = 1234567890 test_array[4] = 12345678900 test_array[5] = 123456789000 test_array[6] = 1000000000000 test_array[7] = 1000000000000:Long For Local i = 0 To 7 Print "Test " + i + ": " + test_array[i] + " > To delimited String: " + IntToStringDelimeted2(test_array[i]) Next Executing:string_test_01.debug.exe Test 0: 1234567 > To delimited String: 1,234,567 Test 1: 12345678 > To delimited String: 12,345,678 Test 2: 123456789 > To delimited String: 123,456,789 Test 3: 1234567890 > To delimited String: 1,234,567,890 Test 4: -539222988 > To delimited String: -539,222,988 Test 5: -1097262584 > To delimited String: -1,097,262,584 Test 6: -727379968 > To delimited String: -727,379,968 Test 7: 1000000000000 > To delimited String: 1,000,000,000,000 |
| ||
As you see in your adjusted code (which could be wrapped in "[.code.]...[./code.]"-blocks) it also needed the fixes mentioned additionally (the minus sign and the "length equals multiple of 3" thing). My proposal is useful if you need to tackle "float/double" values too ... so "123456.789" becomes "123,456.789" then. PS: you might edit the title of your initial post and prepend a "[SOLVED]" so others might find it easier when looking for the same problem/question. bye Ron |