Hexadecimal <--> Decimal Conversion

Scripting sometimes needs a routine to convert hexadecimal into decimal numbers and vice versa
e.g. for reading system parameters oder parsing property list files.
The built-in Perl language provides an easy way to do it:

This code converts a decimal number into a hexdecimal string:

set nDec to 1000
set nHex to do shell script "perl -e 'printf(\"%x\", " & nDec & ")'" --> "3e8"

To get uppercase letters, use %X instead of %x

set nHex to do shell script "perl -e 'printf(\"%X\", " & nDec & ")'" --> "3E8"

To get additional leading zeros, use e.g. %04X for “add zeros for four-digit output”

set nHex to do shell script "perl -e 'printf(\"%04X\", " & nDec & ")'" --> "03E8"

And this code converts a hexdecimal string into a decimal number:

set nHex to "03E8"
set nDec to (do shell script "perl -e 'printf(hex(\"" & nHex & "\"))'") as number --> 1000

Very crisp. :slight_smile:

On my machine, however, set nHex to do shell script “perl -e ‘printf("%X", " & nDec & ")’” → “03E8” even though the “04” hasn’t been specified. :confused:

Here are some similar vanilla AppleScript handlers. Because it wasn’t any extra effort to do so, I’ve made them so that they can handle other bases, including binary and octal.

on convertNumberToBase(aNumber, base)
	if base < 2 or base > 16 then error "convertNumberToBase()" & return & "Base of " & base & " is invalid!"
	
	set s to ""
	repeat until aNumber is 0
		tell aNumber mod base div 1 to if base is greater than 10 and it > 9 then
			set s to character (it - 9) of "ABCDEF" & s
		else
			set s to (it as string) & s
		end if
		set aNumber to aNumber div base
	end repeat
	return s
end convertNumberToBase

convertNumberToBase(1000, 16)
--> "3E8"
on convertBaseToNumber(numberString, base)
	if base < 2 or base > 16 then error "convertBaseToNumber()" & return & "Base of " & base & " is invalid!"
	
	tell numberString to if base is 16 and it starts with "0x" then set numberString to text 3 thru -1
	
	set n to 0
	set s to base ^ ((count of numberString) - 1)
	repeat with c in numberString
		ignoring case
			if base is greater than 10 then tell (offset of c in "abcdef") + 9 to if it > 9 then set c to it
		end ignoring
		set n to n + c * s
		set s to s div base
	end repeat
	return n
end convertBaseToNumber

convertBaseToNumber("1750", 8)
--> 1000

While on the subject of conversions, I converted an algorithm for base 36 (all the numbers and letters as symbols) to this base 27 version just for the fun of it some time ago. I don’t remember where I got the base36 code, I’m sorry to say, and must confess I don’t quite know how the shell script portions of this work but grand sons and grand nephews thought it was fun.


set aName to "ADAM C BELL"
set b10 to base27to10(aName) --> "236811979252785"
set n to do shell script "echo '27 o 10 i " & b10 & " p' | dc"
et b27 to base10to27(n) --> "ADAM C BELL"
display dialog aName & " = " & b10 & return & return & "and going back..." & return & return & b10 & " = " & b27

on base10to27(num)
	script a
		property c : " ABCDEFGHIJKLMNOPQRSTUVWXYZ" -- note leading space
		property n : words of num
		property o : {}
	end script
	
	repeat with i from 1 to count a's n
		set a's o's end to item ((a's n's item i) + 1) of a's c
	end repeat
	"" & a's o
end base10to27

on base27to10(str)
	script a
		property c : " ABCDEFGHIJKLMNOPQRSTUVWXYZ" -- note leading space
		property n : str's items
		property o : {}
	end script
	set k to count str
	repeat with i from 1 to k
		set a's o's end to "(" & ((offset of a's n's item i in a's c) - 1) & " * (27**" & (k - i) & "))"
	end repeat
	set AppleScript's text item delimiters to " + "
	set op to a's o as text
	set AppleScript's text item delimiters to {""}
	do shell script "echo -n " & quoted form of ("print " & op) & " | python -"
end base27to10

A fellow Hitchhiker’s Guide To The Galaxy fan pointed out to me some years ago that 6 times 9 is 42 in base 13. :slight_smile:

OK. Here are a couple of vanilla hex-to-number and number-to-hex handlers that can cope with negatives, using the “signed number” convention. The hex-to-number one appeared the other day in this thread in the OS X forum. The parameters are the hexadecimal string to be converted and a boolean that signifies whether or not the number’s to be regarded as “signed”.

on hexToNum from h given sign:sign
	script o
		property hexChrs : "0123456789abcdef" as Unicode text -- TIDs obey AppleScript's case attribute with Unicode.
		
		on getHexVal(c)
			set AppleScript's text item delimiters to c
			set v to (count text item 1 of hexChrs)
			if (v is 16) then error "Non-hex character(s)."
			
			return v
		end getHexVal
	end script
	
	set astid to AppleScript's text item delimiters
	try
		set h to h as Unicode text -- Speeds up use of TIDs with Unicode 'hexChrs' in o.
		tell o to set n to getHexVal(character 1 of h)
		if (sign) and (n > 7) then set n to n - 16
		
		repeat with i from 2 to (count h)
			tell o to set n to n * 16 + getHexVal(character i of h)
		end repeat
	on error msg number errNum
		set AppleScript's text item delimiters to astid
		error "hexToNum handler: " & msg number errNum
	end try
	set AppleScript's text item delimiters to astid
	
	return n
end hexToNum

hexToNum from "FFFE" without sign -- Treat "FFFE" as an unsigned number.
--> 65534

hexToNum from "FFFE" with sign -- Treat "FFFE" as a signed number.
--> -2

The number-to-hex handler is new today. Its parameters are the number to be hexadecimalised and the required “resolution” (for want of a better word). It produces as many hex digits as are required to express the number and, if necessary, adds leading "0"s or "F"s to pad out the number of digits to the nearest multiple of the “resolution”.

on numToHex(n, res)
	-- Round the input value to the nearest whole-number. (Just in case it's fractional.)
	set n to n div 0.5 - n div 1
	
	script o
		property hexDigits : missing value
		property hexOut : {}
	end script
	
	if (n > -1) then
		-- The number's positive.
		set o's hexDigits to {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}
		
		-- Construct a list of the hexadecimal digits, working backwards from the lowest-order one.
		set widthNow to 0
		repeat
			set beginning of o's hexOut to item (n mod 16 + 1) of o's hexDigits
			set n to n div 16
			set widthNow to widthNow + 1
			-- Finish when n is exhausted and the digit count is a multiple of the resolution. 
			if (n is 0) and (widthNow mod res is 0) then exit repeat
		end repeat
	else
		-- The number's negative.
		set o's hexDigits to {"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "0"}
		set negatives to "89ABCDEF"
		
		-- Construct a list of the hexadecimal digits, working backwards from the lowest-order one.
		set widthNow to 0
		repeat
			set c to item (n mod 16 - 1) of o's hexDigits
			set beginning of o's hexOut to c
			set n to (n + 1) div 16 - 1
			set widthNow to widthNow + 1
			-- Finish when n is exhausted, the digit count is a multiple of the resolution,
			-- and the first digit in the list expresses the negative sign bit. 
			if (n is -1) and (widthNow mod res is 0) and (c is in negatives) then exit repeat
		end repeat
	end if
	
	-- Coerce the digit list to string.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to ""
	set hexOut to o's hexOut as string
	set AppleScript's text item delimiters to astid
	
	return hexOut
end numToHex

numToHex(-255, 1) -- Return only the necessary digits.
--> "F01"

numToHex(-255, 2) -- Pad the number of digits to the nearest multiple of 2.
--> "FF01"

numToHex(1.23456789E+9, 16)
--> "00000000499602D2"

As a pair of complimentary handlers, they should ideally both have either labelled or ordered parameters; but individually, I think they make more sense as they are.

Edit: corrected a sloppy assertion in one of the first script’s comments that TIDs are case-insensitive with Unicode. In fact, they follow the current setting of AppleScript’s ‘case’ attribute. This is ‘ignoring case’ by default, but can of course be changed to ‘considering case’ “ in which case AppleScript’s TIDs will be case-sensitive with Unicode too. With strings, they’re case-sensitive anyway, regardless of the attribute setting.