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.

tobykurk(Posted 2016) [#1]
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


Derron(Posted 2016) [#2]
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


Henri(Posted 2016) [#3]
Hi,

I'm guessing number 2147483648 (2^31) is the hard limit for 32-bit application. Strings of course have no limit.

-Henri


Brucey(Posted 2016) [#4]
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




Henri(Posted 2016) [#5]
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


Derron(Posted 2016) [#6]
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


Brucey(Posted 2016) [#7]
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.


Derron(Posted 2016) [#8]
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


tobykurk(Posted 2016) [#9]
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


Derron(Posted 2016) [#10]
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