Experimenting With java.math.BigInteger In ColdFusion
In the past, I've use Java's BigInteger class (java.math.BigInteger) when dealing with HTTP requests that need to be digitally signed. I've used this class based on other examples that I've seen; but, I've never really taken the time to understand what it does. As such, I thought I would take a little time this morning to get more familiar with the BigInteger class and how it can be used in ColdFusion.
In Java, the core Int data type contains 32 bits. While I don't fully understand how signed vs. unsigned values work, from what I see happen, the first bit in the int is the sign and the remaining 31 bits represent the absolute value. To see the maximum signed Integer that can be represented in Java, we can convert a 31character string of "1"s into an int:
 <cfoutput>

 Int: #inputBaseN( repeatString( "1", 31 ), 2 )#

 </cfoutput>
Running this gives us the following output (I have added the commas for clarity):
Int: 2,147,483,647
If we were to try and create an integer that contained more than 32 bits  for example, changing the 31 in the above code to 33  we would get the following error:
Invalid argument for function InputBaseN. The argument 1 of InputBaseN which is now 111111111111111111111111111111111 must be a valid number in base 2.
While this looks like a Base2 error, the underlying problem is that Java is trying to create an integer using more than 32 bits, which it cannot do. And, because ColdFusion lives on top of Java, it is constrained by the same data type limitations.
ColdFusion does a great job of protecting us from this limitation when it can. For example, the dollarFormat() function will almost never break when using integer values beyond the max value:
 <cfoutput>

 DollarFormat: #dollarFormat( 2147483647 * 2147483647 )#

 </cfoutput>
As you can see here, we are taking the maximum int value and squaring it, before getting its dollar format. Running this code gives us the following output:
DollarFormat: $4,611,686,014,129,999,900.00
Older versions of ColdFusion would have thrown an error here; however, ColdFusion's recent releases have done a great job of trying to hide the data type limitations from us. Of course, this is not always possible (as with our inputBaseN() example). In cases where we simply cannot use the int data type, we can use the BitInteger class.
The BigInteger class allows us to model big integers, perform mathematical operations on them, and convert them to other bases:
 <! Create a big integer representation. >
 <cfset bigInt = createObject( "java", "java.math.BigInteger" ).init(
 javaCast( "string", "255" )
 ) />

 <! Create a char class to get at RADIX constants. >
 <cfset char = createObject( "java", "java.lang.Character" ) />

 <cfoutput>

 <! Output in base10 (decimal) and base16 (hex). >
 Int: #bigInt.toString()#<br />
 Hex: #bigInt.toString( javaCast( "int", 16 ) )#<br />

 <br />

 <! What are the min/max radix we can convert to. >
 Min Radix: #char.MIN_RADIX#<br />
 Max Radix: #char.MAX_RADIX#<br />

 <br />

 <! Demo all the possible radix conversions. >
 <cfloop
 index="baseIndex"
 from="#char.MIN_RADIX#"
 to="#char.MAX_RADIX#"
 step="1">

 Base#baseIndex# =
 #bigInt.toString( javaCast( "int", baseIndex ) )#<br />

 </cfloop>

 </cfoutput>
As you can see here, I am modeling the value, 255, as a BigInteger. Then, I am outputting it in all possible bases. When we run this code, we get the following output:
Int: 255
Hex: ff
Min Radix: 2
Max Radix: 36
Base2 = 11111111
Base3 = 100110
Base4 = 3333
Base5 = 2010
Base6 = 1103
Base7 = 513
Base8 = 377
Base9 = 313
Base10 = 255
Base11 = 212
Base12 = 193
Base13 = 168
Base14 = 143
Base15 = 120
Base16 = ff
Base17 = f0
Base18 = e3
Base19 = d8
Base20 = cf
Base21 = c3
Base22 = bd
Base23 = b2
Base24 = af
Base25 = a5
Base26 = 9l
Base27 = 9c
Base28 = 93
Base29 = 8n
Base30 = 8f
Base31 = 87
Base32 = 7v
Base33 = 7o
Base34 = 7h
Base35 = 7a
Base36 = 73
So far, this is pretty straightforward. This baseconversion is the same thing that inputBaseN() and formatBaseN() do; except for now we can do that with values greater than 32bits.
The BigInteger functionality that seems particularly interesting to me is the fact that it can work with byte arrays. Rather than creating a BigInteger based on a string, we can create it based on a byte array.
 <! Create our string message. >
 <cfset message = "Katie is hot!" />

 <!
 Now, let's create a Big Integer representation of the string
 message using the string's bytes (its binary representation).

 NOTE: The "1" argument is simply to state that the number we
 are representing with the binary value is a positive number.
 >
 <cfset messageInt = createObject( "java", "java.math.BigInteger" ).init(
 javaCast( "int", 1 ),
 toBinary( toBase64( message ) )
 ) />

 <! Now, we can extract the bytes and get the string back. >
 <cfset newMessage = toString( messageInt.toByteArray() ) />

 <! Output the roundtrip converted string. >
 <cfoutput>

 Message: #newMessage#

 </cfoutput>
Here, we are taking a ColdFusion string and converting it into a binary value (byte array). Then, we are creating our BigInteger instance using this byte array. Then, we are getting the bytes back from the BigInteger instance and converting them back to a string. Running the above code gives us the following output:
Message: Katie is hot!
When we move into the land of bits and byte arrays, I start to get lost. This is simply not how I am used to thinking about data. As such, I wanted to try and rework the previous example, but perform the conversions manually rather than letting ColdFusion and BigInteger do all of the heavy lifting. In the following example, I am going to again create a BigInteger based on the binary representation of our string; but this time, I am going to take the integer value of the BigInteger and convert it back into the string manually.
 <! Create our string message. >
 <cfset message = "Katie is hot!" />

 <!
 Now, let's create a Big Integer representation of the string
 message using the string's bytes (its binary representation).

 NOTE: The "1" argument is simply to state that the number we
 are representing with the binary value is a positive number.
 >
 <cfset messageInt = createObject( "java", "java.math.BigInteger" ).init(
 javaCast( "int", 1 ),
 toBinary( toBase64( message ) )
 ) />

 <!
 Output the numeric representation of our byte array
 representation of our string.
 >
 <cfoutput>

 Int: #messageInt.toString()#<br />

 </cfoutput>


 <br />


 <!
 Now, let's create a big int base on our enourmous integer value
 that represents our previous message.
 >
 <cfset bigInt = createObject( "java", "java.math.BigInteger" ).init(
 javaCast( "string", messageInt.toString() )
 ) />

 <!
 Now, let's convert this enormous number to base2 (binary) value.
 When it does this, we will get
 >
 <cfset bits = bigInt.toString( javaCast( "int", 2 ) ) />

 <! Output the bits. >
 <cfoutput>

 Bits: #bits#<br />

 </cfoutput>


 <br />


 <!
 Now that we have our bits, let's break out the bytes. When BigInt
 convert the integer to base2, it only uses as many bits as is
 neeeded to represent the number. As such, the first byte in our
 string may NOT have 8 bits. Therefore, to parse the bytes, we are
 going to leftpad the string with ZEROs to get it to a length of
 8 bits.
 >
 <cfset paddedBits = reReplace(
 rjustify( bits, (8 * ceiling( len( bits ) / 8 )) ),
 " ",
 "0",
 "all"
 ) />

 <!
 Now that we have leftpadded our bit string, we can extract the
 bytes (8 bits to a byte) using a regular expression.
 >
 <cfset bytes = reMatch(
 "\d{1,8}",
 paddedBits
 ) />

 <!
 Now, let's output the message, using each byte as the ASCII
 representation of a character.
 >
 <cfloop
 index="byte"
 array="#bytes#">

 <cfoutput>

 [#chr( inputBaseN( byte , 2 ) )#]

 </cfoutput>

 </cfloop>
When we run this code, we get the following output:
Int: 5972272967631508465388574700577
Bits: 100101101100001011101000110100101100101001000000110 >> 1001011100110010000001101000011011110111010000100001
[K] [a] [t] [i] [e] [ ] [i] [s] [ ] [h] [o] [t] [!]
This is kind of involved, so I'll try to break it out a bit. When we convert our string to a byte array (binary value), each byte represents the ASCII value of a single character within our string. When we then create a BigInteger value based on that byte array, the BigInteger class concatenates all of the bits in our byte array into a single integer.
So, for example, if we had the string "abc", the byte array would look like this:
[ 97 ][ 98 ][ 99 ]
Here, the integar values represent the ASCII characters in the string. In bitformat, this would be:
[ 1100001 ][ 1100010 ][ 1100011 ]
When we create our BigInteger value based on this byte array, it creates an integer that concatenates these bits:
BigInteger == (1100001 & 1100010 & 1100011)
This string of bits then gets modeled as the integer value, 6382179.
In our previous demo, when we end up with this integer value, we then get it back from the BigInteger class in Base2 (our bitrepresentation) and parse the bits back into ASCII values manually.
Byte arrays are hard for me to model mentally. But, this exploration helped me wrap my head around it a bit more. Now, when I go to work with an encrypted value using the BigInteger class, I'll feel a little more confident about what is actually taking place.
Looking For A New Job?
 Wanted: FullTime ColdFusion Developer at Intoria Internet Architects
 Cold Fusion Senior Developer at Edge Information Management
 BackEnd Web DeveloperInformation Technologist at Michigan State University
Reader Comments
I've written soooo many frickin' Base36 encoder/decoder functions. It never even occurred to me that BigInteger might be able to do it natively.
Touche, sir.
@Rick,
Yeah, it seems to have some cool functionality. For standard 32bit ints, you should just be able use formatBaseN() and inputBaseN(); but when you go bigger, it looks like BigInteger does the trick.
The bytearray stuff still trips me up mentally, though.
What if you've got a massive hexidecimal number like a UUID (without any hyphens) and you want to shave 7 characters off it by converting it to base36? It would first have to be converted to decimal but the number is beyond CF's computational powers.
e.g. Take d4938b0d155d011808b8d800d447d431 and convert it to a bigInt and you get 4.53235556861E+076 according to CF  which isn't able to display the entire number. Therefore CF can't perform any further computations on it. But maybe Java could? Or am I making this too complicated. Perhaps there's a Java function to do a straight conversion of base 16 to base 36?
@Gary,
I am not sure I understand what you are trying to do exactly. When you say convert the UUID to a big int, are you using the "byte array" approach that I used in the demo?
Once you have a BigInt value, you should be able to perform math on it... but only using other BigInt values.
ColdFusion does have something called PrecisionEvaluate() which will evaluate expressions that have huge values. I have never used it before; perhaps it's time for me to look into it.
@Gary,
I took some time to dig into the precisionEvaluate() function:
http://www.bennadel.com/blog/2044UsingColdFusionsPrecisionEvaluateFunctionToPerformBigIntegerMath.htm
It's pretty cool, if you're doing bigmath operations.
Post A Comment