I need to input two hexadecimal bytes and manipulate the 4 nibbles that are contained within. For example, the two bytes in the file contain '13BC'x. The first nibble is '1', the second nibble is '3', the third nibble is 'B' and the fourth nibble is 'C'. Due to some *creative* programming, I need to isolate the first nibble and interpret it and then isolate the last three nibbles and interpret them. If the 4 nibbles can be converted to a string, I can proceed from there.
Does anyone know which INFORMAT I should use? Also, what functions or statements would I use to produce a 4 char string of hex characters that I can then manipulate? Eg. SUBSTRING(hex,1,1) for value of '1' etc.
Note: I am a long time mainframe base SAS user, but first time question asker! Thanks in advance for any hints, suggestions or outright answers!
INPUT...
@0553 SUSPCD01 $HEX2.
@0555 SUSPCD02 $HEX2.
@0557 SUSPCD03 $HEX2.
NOTE: Invalid data for SUSPCD01 in line 9 553-554.
NOTE: Invalid data for SUSPCD02 in line 9 555-556.
NOTE: Invalid data for SUSPCD03 in line 9 557-558.
RULE: ----+----1----+----2----+----3----+----4----+----5----+----6
501 ..................................................... . . ..
ZONE 00000000000000000000000000000000000000000000000000001B1B1B00
NUMR 00000000000000000000000000000000000000000000000000003CBCBC00
SUSPCD01= SUSPCD02= SUSPCD03=
They don't seem to understand the difference between a bit and nibble. A bit is one binary digit. A nibble is 4 binary digits. There are 8 bits in a bytes and two nibbles in a byte. There are 16 bits in two bytes and 4 nibbles in 2 bytes.
Let's look at the two byte string with the hex code of '2118'x. If you read that using S370FPIB informat you get the number 8,472. If you take the remainder when dividing by 16**3 you get the 3 least significant digits, which is 280.
If you want the first nibble take integer value of dividing by 16**3. If you want to treat that as a digit in the 16s place of your hex number then just multiply by 16. Or subtract the number you found that has the last three nibbles and divide by 2**8 to remove the second byte (which will be all zeros).
102 data test; 103 string='2118'x ; 104 number = input(string,s370fpib2.); 105 number1 = mod(number,16**3); 106 number2 = (number - number1)/256; 107 put (_all_) (=/); 108 format string $hex4.; 109 run; string=2118 number=8472 number1=280 number2=32
Is the original data in hexadecimal?
I think you can input it as a character type and then process it.
Would the following sample code be helpful?
data sample;
length n 8 c $8;
n=12;c='12';output;
n=255;c='0A';output;
n=65535;c='FF';output;
run;
data _null_;
set sample;
bin_n=put(n,binary16.);
bin_c=put(c,binary16.);
hex_n=put(n,hex4.);
hex_c=put(c,hex4.);
dec_c=input(c,hex4.);
put _all_;
run;
results:
n= 12 c=12 bin_n=0000000000001100 bin_c=0011000100110010 hex_n=000C hex_c=3132 dec_c= 18 _ERROR_=0 _N_=1 n=255 c=0A bin_n=0000000011111111 bin_c=0011000001000001 hex_n=00FF hex_c=3041 dec_c= 10 _ERROR_=0 _N_=2 n=65535 c=FF bin_n=1111111111111111 bin_c=0100011001000110 hex_n=FFFF hex_c=4646 dec_c=255 _ERROR_=0 _N_=3
If you want to read 2 bytes using $CHAR2. informat.
INPUT... @0553 SUSPCD01 $char2. ... ;
If you want to convert two bytes into 4 hexadecimal digits use $HEX4. format.
digits = put(SUSPCD01,$hex4.);
If you want to take one of those digits use SUBSTR(), or CHAR() , function.
first_digit = substr(digits,1,1);
If you want to replace one or more of those digits use SUBSTR() on the left side of an assignment statement .
substr(digits,1,1)='A';
If you need to convert those 4 digits back into two bytes use the $HEX4. informat.
new_SUSPCD01 = input(digits,$hex4.);
If you want the write the new 2 byte string back to a file use the $CHAR2. format.
put ... new_SUSPCD01 $char2. ... ;
The $HEX informat expects only digits 0 to 9 and characters A to F (can also be lowercase) in the input string.
0xBC is clearly not one of these characters.
Read the strings with the $CHAR2. informat, then extract the nibbles like this:
length nib1 $1;
nib1 = substr(put(suspcd01,%hex4.),1,1);
I really appreciate everyone who chipped in, but so far no luck. I am probably not describing the problem very well. The data I am reading in is packed data. I am using this on an IBM mainframe and the data is stored on tape (cartridge.) (Also, there is a LOT of this data, so testing is tough.) I am also using MVS SAS 9.4.
I am providing this extra detail in case it is helpful.
I added this as my first test:
DATA TEST;
NAME='6C6C'X;
NAME2=PUT(NAME,$HEX4.);
PROC PRINT;
The output was this:
The SAS System
Obs NAME NAME2
1 %% 6C6C
Next, I defined the input specification like this:
@0553 SUSPCD01 $CHAR2.
@0555 SUSPCD02 HEX2.
@0557 SUSPCD03 $PHEX2.
All three suspcd values are formatted the same, I am trying three different informats to see which will work.
Then I tried formatting the values as hex characters:
SCTEST1 = PUT(SUSPCD01,$HEX4.);
SCTEST2 = PUT(SUSPCD02,$HEX4.);
WARNING: Variable SUSPCD02 has already been defined as numeric.
SCTEST3 = PUT(SUSPCD03,$HEX4.);
The output was this:
The SAS System
Obs SCTEST1 SCTEST2 SCTEST3 SUSPCD01 SUSPCD02 SUSPCD03
11658
The SAS log gives me an error due to the 2nd SUSPCD informat being invalid:
NOTE: Invalid data for SUSPCD02 in line 9 555-556.
But that is helpful, because it shows the values in those positions:
501 ..................................................... . . ...........
ZONE 00000000000000000000000000000000000000000000000000001B1B1B00000000000
NUMR 00000000000000000000000000000000000000000000000000003CBCBC00000000000
121 The SAS System
RULE: ----+----1----+----2----+----3----+----4----+----5----+----6----+----
The values in the data for the positions defined are packed hexadecimal (eg. 13BC, for the first value) and I want to read them in and process those values like a string. I really thought that SCTEST1 was going to work. I feel like I should be reading the data using a numeric informat for packed hex and then writing out that number as a hex string. I really thought that either SUSPCD01 or SUSPCD03 from my example here was going to work. I am feeling both lost and pretty close at the same time. Thanks again for all your help so far.
If you have a value like 13BC then it is not packed decimal as B is not a valid decimal digit.
Do you mean the values are BINARY?
Use the PIB informat to convert the bytes into integers.
Use the PIB format to convert the integers into bytes.
56 data test; 57 string='13BC'x ; 58 number = input(string,pib2.); 59 string2 = put(number,pib2.); 60 put string= $hex. number= comma12. number= hex4. string2= $hex. ; 61 run; string=13BC number=48,147 number=BC13 string2=13BC NOTE: The data set WORK.TEST has 1 observations and 3 variables.
You also have to decide whether to treat the values as "bigendian" or "littlendian".
62 data test; 63 string='13BC'x ; 64 number1 = input(string,pib2.); 65 number2 = input(string,s370fpib2.); 66 put string= $hex. 67 / number1= comma12. number1= hex4. 68 / number2= comma12. number2= hex4. 69 ; 70 run; string=13BC number1=48,147 number1=BC13 number2=5,052 number2=13BC NOTE: The data set WORK.TEST has 1 observations and 3 variables.
When you read 2 characters with the $CHAR2. informat, you get those characters, period. If you get something else, then your positioning is off.
If you need to read 2 characters from position 553, first read a dummy variable with $CHAR552. and then your variable. Next, display the variable in your data step with
put var $hex4.;
so that you see the true content.
I did as you suggested.
INPUT...
@0553 SUSPCD01 $CHAR2.
@0555 SUSPCD02 $CHAR2.
@0557 SUSPCD03 $CHAR2.
PROC PRINT;
FORMAT SUSPCD01 $HEX4.;
FORMAT SUSPCD02 $HEX4.;
FORMAT SUSPCD03 $HEX4.;
VAR SUSPCD01 SUSPCD02 SUSPCD03 SCTEST1 SCTEST2 SCTEST3;
Results:
Obs SUSPCD01 SUSPCD02 SUSPCD03 SCTEST1 SCTEST2 SCTEST3
11658 4040 4040 4040
The values in those 3 suspcd fields are not blanks. They are hex values that do not correspond to EBCDIC characters. I think the $CHAR2. informat is confused by the values and is defaulting it to blanks. So, first off, I think it needs to be a different informat, not $CHAR2., but I still do not know which informat will leave the internal values intact. Also, the $HEX4. output is fine when I use a character based informat, but I get the feeling I cannot do that. So, I still find myself looking for the correct informat and correct way to display the hex values as a character string. I appreciate the thoughtfulness in your response Kurt and I am frustrated with my own inability to figure this out (usually *I* am the one people go to with SAS questions!)
'40'x is EBCDIC code for a space.
Did you get transcoding notes in the log or not?
Are you reading file using RECFM=F?
How are you accessing the file? Did someone transfer it from the IBM mainframe to your SAS server as a TEXT file instead of a BINARY file?
Um, no offense, but I did not accept this as the solution. This is twice now where I am not even on the page and the question I have posted is marked as solve. The first time I thought I may have done it accidentally, but not this time.
Anyway, yes '40'x is a space, which is why I suspect that the informat is translating the hex values. The field does not contain '40'x.
No transcoding notes in the SAS log.
Yes, file is fixed FB.
I am accessing it on the mainframe where I am running the program. No SAS server, just mainframe SAS and my crummy old file on cartridge.
So check in order. The options on your INFILE statement. The JCL on the DD statement that is pointing at the file.
What do you see in the _INFILE_ automatic variable at those positions? For example to check the two bytes in columns 533 and 534 for the record use something like:
....
input @ ;
string = substr(_infile_,0533,2);
put string $hex4.;
Try setting the infile option ENCODING='ANY'.
Try this test program. The ENCODING= option does appear to be needed but it does no harm.
filename test temp;
data _null_;
string='2118'x ;
file test recfm=f lrecl=2;
put string $char2.;
run;
data test;
infile test recfm=f lrecl=2 encoding='any';
input string $char2. @1 x s370fpib2. ;
number = input(string,s370fpib2.);
number1 = mod(number,16**3);
number2 = (number - number1)/256;
put (_all_) (=/);
format string $hex4.;
run;
I added the encoding = 'any' option to the INFILE statement. It did not seem to affect it one way or another.
I am continuing to try different INFORMATs. I tried the one you referenced, s370fpib2. because, well because I am getting desperate here! 🙂
I have also tried PIB2., IBR2. and IB2.
None of these INFORMATs fail (no warnings, no hard errors) but none of them produce characters when I write them out with a HEX4. FORMAT.
FWIW, regarding the solved vs unsolved issue, I do not see any button to unselect the answer. My browser is Chrome. I am new to the site though, so I expect I will figure that out at some point.
Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.