IBM i > DEVELOPER > GENERAL

Converting Numeric to Character


If like us you're a regular visitor on some of the RPG-related Internet lists, you've probably noticed that some topics tend to come in waves. Recently we've noticed a number of questions relating to converting numeric fields from and to character representations.

We're not quite certain why this is, but we assume it relates to increased use of Web services, Web applications, XML, JSON and other forms of data interchange that rely on character-level data. So, we decided that it was about time to revisit this topic and talk about a few of the ways in which RPG can handle such conversions.

As it turns out, there's a lot more to think about in character to numeric conversions, so that will be the subject of a future article. For now let's start with the most common requirement--converting numerics to character form.

The DS Route

The simplest and most basic method is to use a data structure (DS). This works because the underlying binary values for a numeric digit in a zoned decimal field are the same as those of that same digit in a character field (i.e., hex F0 to F9).

In the example below we defined the numeric field numZip as zoned (S) and placed it in the DS charZip. Converting a ZIP Code from numeric to the character equivalent is achieved by moving the numeric field to numZip and then extracting the result from charZip.

     d charZip         ds
     d  numZip                        5s 0

       // Convert via DS
       numZip = zipCode;
       dsply ( 'DS conversion of 02128 is (' + charZip + ')' );

Using a DS in this way is far and away the simplest approach, but the intent of the code is less than obvious and you are reliant on naming conventions and/or comments to let your fellow programmers know exactly what is going on.

If we were going to be using this approach in practice, then we would prefer to define the DS within a subprocedure to make the conversion activity more obvious within the logic. The call to that subprocedure would look like this:

    Zip = ZipToChar (zipCode);
   dsply ( 'DS via subproc conversion of 02128 is (' + Zip + ')' );  

The subprocedure would look something like this.

     p ZipToChar       b

     d                 pi                  Like(charZip)
     d  numZipIn                           Like(numZipCode) Const

     d charZip         ds
     d  numZipCode                    5s 0

         numZipCode = numZipIn;
         return charZip;

     p ZipToChar       e        

By using the keyword Const on the parameter definition (numZipIn) we have also made the subprocedure capable of accepting both packed and zoned numbers as input.

While the DS approach works perfectly for things such as ZIP code, it has a significant restriction in that it can only be used for positive numbers without decimal places. Negative values will cause the last digit to appear as an apparently "strange" character, and decimal points will simply not appear at all. So what's the alternative?

What We Need Is a BIF

When people go looking to Built-In-Functions (BIFs) for the solution, the first they usually encounter is %Char. That particular BIF is useful in numeric conversions such as adding a "Your balance of $xxxxx is seriously overdue ..." message to the bottom of an invoice. It is also useful when converting numerics to character to include them, for example, in a CSV file. But it does have one feature that is a severe disadvantage in cases such as our ZIP Code example.

As Jon discovered to his dismay many moons ago, U.S. ZIP Codes can start with leading zeros. That is a problem here because %Char not only converts from numeric to character but also strips any leading zeros. The result is that a ZIP Code such as 02128 will be rendered by %Char as 2128. Not what we want.

Enter %EditC

So what’s the alternative? %EditC is the answer. The "C" in this case stands for Code. %EditC will apply an edit code to the numeric value and return the result. By edit code we mean any of the codes that you can use in display or printer files or on O-specs. This gives you a tremendous amount of flexibility and should meet nearly all of your formatting needs. If/when it does not, you can always use %EditC's sibling %EditW, which applies an edit word of your choosing to the numeric field. We won't be discussing %EditW here as it is rarely needed in a conversion context, which is what we are focusing on.

So which code to use? If you study those available, you will find that the one we need to reliably perform our ZIP Code conversion is code 'X'. This will retain leading zeros while not inserting thousands separators. Our ZIP Code would look very strange if formatted as 02,128! In fact code 'X' is the one you will probably use most of the time when performing these types of numeric conversion.

Now that we have a satisfactory solution for our ZIP Code and similar conversions where leading zeros are needed, such as for dates, what about other cases where more extensive editing is required? For example if we actually require a thousands separator? In that case we could use codes A, J, or N depending on how we wanted negative values to be handled. All three will return the same result for positive numbers. Code A will add the characters CR to negative amounts whereas J will add a trailing minus and N a floating leading minus sign. So the negative value -1,234.56 would result in 1,234.56CR (code A), 1,234.56- (code J) and -1,234.56 (code N). Apologies in advance to all those of you who already knew all of that, but you can take comfort from the fact that you're probably in the minority! There is however an important point to be made here.

Unlike %Char, %EditC (and indeed %EditW) always returns a fixed-length result. So for any given source field and edit code combination they will produce a result of the same length with leading and/or trailing blanks as appropriate. In our example above, if we were using a 7,2 numeric field, both codes J and N would produce a 10-character result while code A would produce one 11 characters long. As a result you will frequently need to code %EditC in combination with %Trim in order to strip off the leading and trailing blanks when you concatenate the result into a string:

   csvString += ',' + %Trim ( %EditC ( numField :  'N' ) ); 

The sample program that accompanies this article contains examples of all three of these edit codes in action. You'll see that both fixed and free-form data definition versions have been included.

One thing we should note with regard to %EditC that surprises, nay disappoints, many folk is that it is not possible to use a variable to supply the edit code. The code used must be able to be resolved at compile time.

Alternatives?

Other methods are available to handle the conversion. For example, prototyping and using MI functions could be used, but in the vast majority of cases that would just be overkill.

At this point you might be wondering why we haven't mentioned the good old MOVE op-code since we no longer need to surround it with /Free and /End-Free to make it available.

One reason is that MOVE is just bad news from a maintenance perspective. When you see a MOVE op-code in a program you really have no idea what is actually going on without investigating the data types and sizes of both fields. Is it changing the data type? Dropping bits off the end? Joining two fields together? Overlaying part of the data?

The second reason is that it would make the code just plain ugly and if, at some point in the future, you decided to convert the code to fully free-from, you would either have to fix the problem anyway and use the appropriate BIF or place the code in a second source member and /Copy it in <shudder>.

So just do it right now—accept that the compiler team didn’t support MOVE in /Free RPG for good reasons and get on with your life.

Next Time

In our next article, we’ll examine the reverse situation, converting character fields to numeric.

Jon Paris is a technical editor with IBM Systems Magazine and co-owner of Partner400.

Susan Gantner is a technical editor with IBM Systems Magazine and co-owner of Partner400.



Like what you just read? To receive technical tips and articles directly in your inbox twice per month, sign up for the EXTRA e-newsletter here.


comments powered by Disqus

Advertisement

Advertisement

2019 Solutions Edition

A Comprehensive Online Buyer's Guide to Solutions, Services and Education.

Are You Multilingual?

Rational enables development in multiplatform environments

IBM Systems Magazine Subscribe Box Read Now Link Subscribe Now Link iPad App Google Play Store
IBMi News Sign Up Today! Past News Letters