Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with:

Experimenting With java.math.BigInteger In ColdFusion

By Ben Nadel on
Tags: 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 31-character 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 base-conversion is the same thing that inputBaseN() and formatBaseN() do; except for now we can do that with values greater than 32-bits.

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 round-trip 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 re-work 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 left-pad 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 left-padded 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 bit-format, 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 bit-representation) 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.




Reader Comments

I've written soooo many frickin' Base-36 encoder/decoder functions. It never even occurred to me that BigInteger might be able to do it natively.

Touche, sir.

Reply to this Comment

@Rick,

Yeah, it seems to have some cool functionality. For standard 32-bit ints, you should just be able use formatBaseN() and inputBaseN(); but when you go bigger, it looks like BigInteger does the trick.

The byte-array stuff still trips me up mentally, though.

Reply to this Comment

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?

Reply to this Comment

@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.

Reply to this Comment

As always Ben - You've helped me out of another pickle!

I'm using bits for filtering leads in our CRM - All was good before I ran out of bits and had to go to the magic number 2147483648 and my world fell to pieces (Well, the code stopped working).

Thanks again

Martin

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.