This is a fully functional PowerShell script I wrote not long ago that converts data (numerical data) among 4 data formats: binary, decimal, hexadecimal and ipv4.
It supports 12 conversions, it can convert number from each type to the other three type, it can convert:
binary to decimal | binary to hexadecimal | binary to ipv4
decimal to binary | decimal to hexadecimal | decimal to ipv4
hexadecimal to binary | hexadecimal to decimal | hexadecimal to ipv4
ipv4 to binary | ipv4 to decimal | ipv4 to hexadecimal
And I have tested the script numerous times and confirmed it gives right answers, I wrote the script all by myself without any help from anyone and without using the inbuilt methods of PowerShell, of course I know these .Net methods and I just don't use them in my script because that wouldn't be a programming challenge and won't help me improve my skills, though of course I have checked the results of my script against those of the built-in methods and found no discrepancies.
So here is the script:
function NumConversion {
param(
[parameter(valuefrompipeline=$true, mandatory=$true, position=0)] [ValidateNotNull()] [string] $data,
[parameter(valuefrompipeline=$true, mandatory=$true, position=1)] [ValidateNotNull()] [string] $task
)
begin {
$binhex = @{
'0000' = '0'
'0001' = '1'
'0010' = '2'
'0011' = '3'
'0100' = '4'
'0101' = '5'
'0110' = '6'
'0111' = '7'
'1000' = '8'
'1001' = '9'
'1010' = 'a'
'1011' = 'b'
'1100' = 'c'
'1101' = 'd'
'1110' = 'e'
'1111' = 'f'
}
$dechex = @{
'0' = '0'
'1' = '1'
'2' = '2'
'3' = '3'
'4' = '4'
'5' = '5'
'6' = '6'
'7' = '7'
'8' = '8'
'9' = '9'
'10' = 'a'
'11' = 'b'
'12' = 'c'
'13' = 'd'
'14' = 'e'
'15' = 'f'
}
}
process {
$bin2dec = {
param($bin)
$power = 1
$dec = 0
[int[]]$bits = $bin -split "(0|1)" | where {$_ -ne ""}
for ($i = $bits.count - 1; $i -ge 0; $i--) {
$bit = $bits[$i]
$dec += $bit * $power
$power *= 2
}
$dec
}
$bin2hex = {
param($bin)
$i = 0
if ($bin.Length % 4 -ne 0) {
while ($i -lt $bin.Length) {$i += 4}
$bin = '0' * ($i - $bin.Length) + $bin
}
$bin = $bin -split '([01]{4})' | where {$_ -ne ''}
$hex = ($bin | % { $binhex.$_ }) -join ''
$hex
}
$bin2ip = {
param($bin)
if ($bin.length -lt 32) { $bin = '0' * (32 - $bin.length) + $bin }
[array]$bin = $bin -split '([01]{8})' | where {$_ -ne ''}
[array]$ip = foreach($seg in $bin) { &$bin2dec -bin $seg }
$ip = $ip -join '.'
$ip
}
$dec2bin = {
param($dec)
[array]$bits = for ($i = 1; $i -le $dec; $i*=2) {$i}
[string[]]$bin = for($i = $bits.count - 1; $i -ge 0; $i--) {
$bit = $bits[$i]
if ($dec -ge [double]$bit) {1; $dec -= $bit}
else {0}
}
[string]$bin = $bin -join ''
$bin
}
$dec2hex = {
param($dec)
[array]$bits = for ($i = 1; $i -le $dec; $i *= 16) {$i}
[string[]]$hex = for ($i = $bits.count - 1; $i -ge 0; $i--) {
$bit = $bits[$i]
if ($dec -ge $bit) {
$num = [math]::floor($dec / $bit)
$dec = $dec % $bit
$dechex.([string]$num)
}
else {0}
}
[string]$hex = $hex -join ""
$hex
}
$dec2ip = {
param($dec)
[array]$ip = for ($i = 0; $i -le 3; $i++) {
$seg = $dec % 256
$dec = ($dec - $seg) / 256
$seg
}
$ip = for ($i = 3; $i -ge 0; $i--) { $ip[$i] }
$ip = $ip -join '.'
$ip
}
$hex2bin = {
param($hex)
[array]$hexa = $hex -split '(\w{1})' | where {$_ -ne ''}
$bin = for ($i = 0; $i -lt $hexa.count; $i++) {
$binhex.keys | where {$binhex.$_ -eq $hexa[$i]}
}
$bin = $bin -join ''
$bin
}
$hex2dec = {
param($hex)
[array]$bits = for($i = 0; $i -lt $hex.length; $i++){[math]::pow(16, $i)}
$dec = 0
$hexa = $hex -split '(\w{1})' | where {$_ -ne ''}
for (($i = 0), ($j = $hexa.count - 1); $i -lt $hexa.count; $i++, $j--) {
$num = [double]($dechex.keys | where {$dechex.$_ -eq $hexa[$j]})
$bit = $bits[$i]
$dec = $dec + $num * $bit
}
$dec
}
$hex2ip = {
param($hex)
if ($hex.length -lt 8) {$hex = '0' * (8 - $hex.length) + $hex }
[array]$hexa = $hex -split '(\w{2})' | where {$_ -ne ''}
[array]$ip = foreach($seg in $hexa) { &$hex2dec -hex $seg }
$ip = $ip -join '.'
$ip
}
$ip2bin = {
param($ip)
$ip = $ip.split('.')
[array]$bin = foreach ($seg in $ip) {
[int]$num = $seg
[array]$bits = for ($i = 128; $i -ge 1; $i /= 2) {$i}
$bits = $bits | % {
if ($num -ge [int64]$_) {1; $num -= $_}
else {0}
}
[string]$bits = $bits -join ""
$bits
}
$bin = $bin -join ''
$bin
}
$ip2dec = {
param($ip)
$ip = $ip.split('.')
$dec = 0
$base = 16777216
foreach ($seg in $ip) {
$dec += [int]$seg * $base
$base = $base / 256
}
$dec
}
$ip2hex = {
param($ip)
$ip = $ip.split('.')
[string[]]$hex = foreach ($seg in $ip) {
$byte = &$dec2hex -dec $seg
if ($byte.length -lt 2) { $byte = '0' + $byte }
$byte
}
$hex = $hex -join ''
$hex
}
switch ($task)
{
'bd' { [string]$bin = $data; &$bin2dec -bin $bin }
'bh' { [string]$bin = $data; &$bin2hex -bin $bin }
'bi' { [string]$bin = $data; &$bin2ip -bin $bin }
'db' { [double]$dec = $data; &$dec2bin -dec $dec }
'dh' { [double]$dec = $data; &$dec2hex -dec $dec }
'di' { [double]$dec = $data; &$dec2ip -dec $dec }
'hb' { [string]$hex = $data; &$hex2bin -hex $hex }
'hd' { [string]$hex = $data; &$hex2dec -hex $hex }
'hi' { [string]$hex = $data; &$hex2ip -hex $hex }
'ib' { [string]$ip = $data; &$ip2bin -ip $ip }
'id' { [string]$ip = $data; &$ip2dec -ip $ip }
'ih' { [string]$ip = $data; &$ip2hex -ip $ip }
}
}
}
$data = $Args[0]
$task = $Args[1]
NumConversion $data $task
I came up with the logics and the algorithms all by myself;
Usage:
Save the script as .\NumConversion.ps1
, it uses 12 keywords to identify which process to carry out, the 12 switches are (in the order of the table shown above):
bd | bh | bi
db | dh | di
hb | hd | hi
ib | id | ih
Now if you want to convert something you just give it two arguments, the data as the first argument and the keyword as the second argument, for example, if you want to convert the ipv4 address 127.0.0.1
to binary:
.\NumConversion.ps1 127.0.0.1 ib
And it will return:
01111111000000000000000000000001
I want your thoughts on my script, can it be more performant, how can it be improved? Can it be shorter and still be clear? Can the code be simplified? I am still very new to all these programming thing, and I want to know how my skills can be improved. Thank you.
I will briefly explain the logic I use in these algorithms.
I found 4 binary bits equal to 1 hexadecimal bit, namely 4 binary bits can represent numbers from 0 to 15, or 2^4 - 1, and integers from 0 to 15 can be represented by 1 hexadecimal bit, and there can be one to one correspondence between every four binary bits and one hexadecimal bit, and binary numbers can be just split into chunks of four (if the string is padded into multiples of four) and translated into hexadecimal bits, because every 4 binary bits segment has the base unit 16 times the previous unit, exactly like 1 hexadecimal bit.
And I think IPv4 is sort of base-256, because from left to right, each consecutive segment has the base 256 times of the previous segment, I understand IPv4 is a 4-byte number and 1 byte equals to 8 binary bits and 2 hexadecimal bits, because 8 binary bits can represent integers 0 - 255 (2^8 - 1), and 2 hexadecimal bits can also represent integers 0 - 255 (16^2 - 1), so there is a one-one correspondence among 1 byte of ipv4 and 2 bits of hexadecimal and 8 bits of binary, and I can just split the string (if it is padded first) and translate the value.
I mainly use split and padding, I split the binary/hexadecimal/ipv4 string into chunks of appropriate size and store the values in an array, then translate the segments one by one and join strings.
The codes to convert binary, hexadecimal and ipv4 are similar, I wonder if I can use one script block to achieve the conversion processes and use a parameter to determine which conversion to run.
I wonder if I can use less code to get the same results using the same logic.
Update: Yeah, thanks for JosefZ's feedback, the two problem are now solved in my new edit.
About the first issue, that was unexpected and unintended, and that was definitely a bug, and I was able to fix it by explicitly declare the data type of the variable $dec
to appropriate data type; Now I have improved the type casting of all the variables, the bug is fixed.
And about the second issue, that was definitely not a bug, however I definitely haven't intended for it to even give an output, it was working as it should be.
You all know that saying: garbage in, garbage out; you inputted wrong data, data that the algorithm isn't designed to process, you must expect it to give wrong result.
I thought everyone that will be using my script will be in their clear minds and won't input the wrong data to the script, so I didn't implement exception handling functions.
I was lazy, now I have fixed it, the script will now through out 7 error messages depending on 7 possible situations, the process won't run if you don't input correctly formatted data in one of the four data types possible respective to the process;
And if you use one of the three functions that converts data into IPv4, and the value exceeds the maximum possible value (11111111111111111111111111111111 for binary, 4294967295 for decimal and ffffffff for hexadecimal), the process won't run.
Now everything is definitely working as intended, but there are even more duplicate lines of code that can be improved, but I am working on other projects now and I don't have time to improve the code.
The updated code:
function NumConversion {
param(
[parameter(valuefrompipeline=$true, mandatory=$true, position=0)] [ValidateNotNull()] [string] $data,
[parameter(valuefrompipeline=$true, mandatory=$true, position=1)] [ValidateNotNull()] [string] $task
)
begin {
$binhex = @{
'0000' = '0'
'0001' = '1'
'0010' = '2'
'0011' = '3'
'0100' = '4'
'0101' = '5'
'0110' = '6'
'0111' = '7'
'1000' = '8'
'1001' = '9'
'1010' = 'a'
'1011' = 'b'
'1100' = 'c'
'1101' = 'd'
'1110' = 'e'
'1111' = 'f'
}
$dechex = @{
'0' = '0'
'1' = '1'
'2' = '2'
'3' = '3'
'4' = '4'
'5' = '5'
'6' = '6'
'7' = '7'
'8' = '8'
'9' = '9'
'10' = 'a'
'11' = 'b'
'12' = 'c'
'13' = 'd'
'14' = 'e'
'15' = 'f'
}
}
process {
$bin2dec = {
param($bin)
[string]$bin = $bin
if ($bin -match "[01]{$($bin.length)}") {
$power = 1
$dec = 0
[int[]]$bits = $bin -split "(0|1)" | where {$_ -ne ""}
for ($i = $bits.count - 1; $i -ge 0; $i--) {
$bit = $bits[$i]
$dec += $bit * $power
$power *= 2
}
$dec
} else { write-error -message "InvalidData: The inputted string isn't a valid binary string, process will now stop." -Category InvalidData; break }
}
$bin2hex = {
param($bin)
[string]$bin = $bin
if ($bin -match "[01]{$($bin.length)}") {
$i = 0
if ($bin.Length % 4 -ne 0) {
while ($i -lt $bin.Length) {$i += 4}
$bin = '0' * ($i - $bin.Length) + $bin
}
[array]$bin = $bin -split '([01]{4})' | where {$_ -ne ''}
$hex = ($bin | % { $binhex.$_ }) -join ''
$hex
} else { write-error -message "InvalidData: The inputted string isn't a valid binary string, process will now stop." -Category InvalidData; break }
}
$bin2ip = {
param($bin)
[string]$bin = $bin
if ($bin -match "[01]{$($bin.length)}") {
if ($bin -eq ($bin | select-string -pattern "[01]{1,32}").matches.value) {
if ($bin.length -lt 32) { $bin = '0' * (32 - $bin.length) + $bin }
[array]$bin = $bin -split '([01]{8})' | where {$_ -ne ''}
[array]$ip = foreach($seg in $bin) { &$bin2dec -bin $seg }
$ip = $ip -join '.'
$ip
} else { write-error -message "LimitsExceeded: The value of the inputted binary string exceeds the maximum IPv4 value possible, process will now stop.(maximum value allowed: 11111111111111111111111111111111)" -Category LimitsExceeded; break }
} else { write-error -message "InvalidData: The inputted string isn't a valid binary string, process will now stop." -Category InvalidData; break }
}
$dec2bin = {
param($dec)
if ([string]$dec -match "[0-9]{$($([string]$dec).Length)}") {
[double]$dec = $dec
[array]$bits = for ($i = 1; $i -le $dec; $i*=2) {$i}
[string[]]$bin = for($i = $bits.count - 1; $i -ge 0; $i--) {
$bit = $bits[$i]
if ($dec -ge [double]$bit) {1; $dec -= $bit}
else {0}
}
[string]$bin = $bin -join ''
$bin
} else { write-error -message "InvalidData: The inputted string isn't a valid binary string, process will now stop." -Category InvalidData; break }
}
$dec2hex = {
param($dec)
if ([string]$dec -match "[0-9]{$($([string]$dec).Length)}") {
[double]$dec = $dec
[array]$bits = for ($i = 1; $i -le $dec; $i *= 16) {$i}
[string[]]$hex = for ($i = $bits.count - 1; $i -ge 0; $i--) {
$bit = $bits[$i]
if ($dec -ge $bit) {
$num = [math]::floor($dec / $bit)
$dec = $dec % $bit
$dechex.([string]$num)
}
else {0}
}
[string]$hex = $hex -join ""
$hex
} else { write-error -message "InvalidData: The inputted string isn't a valid decimal natural number, process will now stop." -Category InvalidData; break }
}
$dec2ip = {
param($dec)
if ([string]$dec -match "[0-9]{$($([string]$dec).Length)}") {
[int64]$dec = $dec
if ($dec -le 4294967295) {
[array]$ip = for ($i = 0; $i -le 3; $i++) {
$seg = $dec % 256
$dec = ($dec - $seg) / 256
$seg
}
$ip = for ($i = 3; $i -ge 0; $i--) { $ip[$i] }
$ip = $ip -join '.'
$ip
} else { write-error -message "LimitsExceeded: The value of the inputted decimal natural number exceeds the maximum IPv4 value possible, process will now stop. (maximum value allowed: 4294967295)" -Category LimitsExceeded; break }
} else { write-error -message "InvalidData: The inputted string isn't a valid decimal natural number, process will now stop." -Category InvalidData; break }
}
$hex2bin = {
param($hex)
[string]$hex = $hex
if ($hex -match "[0-9a-f]{$($hex.length)}") {
[array]$hexa = $hex -split '(\w{1})' | where {$_ -ne ''}
$bin = for ($i = 0; $i -lt $hexa.count; $i++) {
$binhex.keys | where {$binhex.$_ -eq $hexa[$i]}
}
$bin = $bin -join ''
$bin
} else { write-error -message "InvalidData: The inputted string isn't a valid hexadecimal number, process will now stop." -Category InvalidData; break }
}
$hex2dec = {
param($hex)
[string]$hex = $hex
if ($hex -match "[0-9a-f]{$($hex.length)}") {
[array]$bits = for($i = 0; $i -lt $hex.length; $i++){[math]::pow(16, $i)}
$dec = 0
[array]$hexa = $hex -split '(\w{1})' | where {$_ -ne ''}
for (($i = 0), ($j = $hexa.count - 1); $i -lt $hexa.count; $i++, $j--) {
$num = [double]($dechex.keys | where {$dechex.$_ -eq $hexa[$j]})
$bit = $bits[$i]
$dec = $dec + $num * $bit
}
$dec
} else { write-error -message "InvalidData: The inputted string isn't a valid hexadecimal number, process will now stop." -Category InvalidData; break }
}
$hex2ip = {
param($hex)
[string]$hex = $hex
if ($hex -match "[0-9a-f]{$($hex.length)}") {
if ($hex -eq ($hex | select-string -pattern "[0-9a-f]{1,8}").matches.value) {
if ($hex.length -lt 8) {$hex = '0' * (8 - $hex.length) + $hex }
[array]$hexa = $hex -split '(\w{2})' | where {$_ -ne ''}
[array]$ip = foreach($seg in $hexa) { &$hex2dec -hex $seg }
$ip = $ip -join '.'
$ip
} else { write-error -message "LimitsExceeded: The value of the inputted hexadecimal number exceeds the maximum IPv4 value possible, process will now stop. (maximum value allowed: ffffffff)" -Category LimitsExceeded; break }
} else { write-error -message "InvalidData: The inputted string isn't a valid hexadecimal number, process will now stop." -Category InvalidData; break }
}
$ip2bin = {
param($ip)
[string]$ip = $ip
if ($ip -match '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)') {
[array]$ip = $ip.split('.')
[array]$bin = foreach ($seg in $ip) {
[int]$num = $seg
[array]$bits = for ($i = 128; $i -ge 1; $i /= 2) {$i}
$bits = $bits | % {
if ($num -ge [int64]$_) {1; $num -= $_}
else {0}
}
[string]$bits = $bits -join ""
$bits
}
$bin = $bin -join ''
$bin
} else { write-error -message "InvalidData: The inputted string isn't a valid IPv4 string, process will now stop." -Category InvalidData; break }
}
$ip2dec = {
param($ip)
[string]$ip = $ip
if ($ip -match '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)') {
[array]$ip = $ip.split('.')
$dec = 0
$base = 16777216
foreach ($seg in $ip) {
$dec += [int]$seg * $base
$base = $base / 256
}
$dec
} else { write-error -message "InvalidData: The inputted string isn't a valid IPv4 string, process will now stop." -Category InvalidData; break }
}
$ip2hex = {
param($ip)
[string]$ip = $ip
if ($ip -match '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)') {
[array]$ip = $ip.split('.')
[string[]]$hex = foreach ($seg in $ip) {
$byte = &$dec2hex -dec $seg
if ($byte.length -lt 2) { $byte = '0' + $byte }
$byte
}
$hex = $hex -join ''
$hex
} else { write-error -message "InvalidData: The inputted string isn't a valid IPv4 string, process will now stop." -Category InvalidData; break }
}
switch ($task)
{
'bd' { $bin = $data; &$bin2dec -bin $bin }
'bh' { $bin = $data; &$bin2hex -bin $bin }
'bi' { $bin = $data; &$bin2ip -bin $bin }
'db' { $dec = $data; &$dec2bin -dec $dec }
'dh' { $dec = $data; &$dec2hex -dec $dec }
'di' { $dec = $data; &$dec2ip -dec $dec }
'hb' { $hex = $data; &$hex2bin -hex $hex }
'hd' { $hex = $data; &$hex2dec -hex $hex }
'hi' { $hex = $data; &$hex2ip -hex $hex }
'ib' { $ip = $data; &$ip2bin -ip $ip }
'id' { $ip = $data; &$ip2dec -ip $ip }
'ih' { $ip = $data; &$ip2hex -ip $ip }
default {write-error -message "InvalidOperation: The operation specified is undefined, process will now stop." -Category InvalidOperation}
}
}
}
$data = $Args[0]
$task = $Args[1]
NumConversion $data $task
NumConversion 1 hd
orNumConversion 127.0.0.1 ih
. And compare edge examples (here I agree that1
isn't an IP-like string…)NumConversion 1 ih
versusNumConversion 1 id
. \$\endgroup\$NumConversion (NumConversion 1 ih) hi
returns0.0.0.1
whileNumConversion (NumConversion 1 id) di
yields1.0.0.0
. It's an inconsistency, isn't it? \$\endgroup\$System.Net.IPAddress
has bothHostToNetworkOrder()
andNetworkToHostOrder()
. \$\endgroup\$