X++ Function num2Str: Difference in .NET CIL Mode

SUMMARY: You cannot pass the value of -1 for the ‘decimals’ parameter to the num2str X++ function when the function is run in .NET CIL mode, in Microsoft Dynamics AX 2012.




In Microsoft Dynamics AX 2012, some of your X++ code can run as either...

A. Traditional interpreted p-code, or now as

B. Microsoft .NET CIL (common intermediate language).


There are a small number of cases where the behavior of specific X++ code differs between these two modes. These differences have been described in earlier blog entries such as one here about AX collections.

One more difference concerns the X++ language built-in function named num2str.


num2Str function, parameters demonstration


The num2str function inputs a primitive ‘real’ type such as 1234.58, and it outputs a ‘str’ type such as “1,234.6”. The function also inputs two int parameters and two enum parameters to choose the output string format details.


On MSDN the documentation for the num2str function is here.  The following table gives the names and descriptions of the parameters for num2str:


Parameter Name



The real number to convert to a string.


The minimum number of characters required in the text.


The required number of decimal places.


A DecimalSeparator enumeration value.

(Documentation here . )


A ThousandSeparator enumeration value.

(Documentation here . )


Next below is tiny job (in AOT > Jobs) that demonstrates the num2Str parameters:


static void Job7(Args _args) // X++ Job in the AOT, interpreted p-code.
    str moneyString;
    real moneyReal = 12345.68;

moneyString = num2str
        -1, // Minimum number of characters to output into the string.
        -1, // Required number of digits to the right of the decimal.

DecimalSeparator::Dot, // 1
        ThousandSeparator::Comma / / 2

/**** InfoLog window, when parameters are -1 and -1
Message (11:44:17 am)


In the preceding X++ job, consider the two parameters that have -1 as their value. This is an informal value that asks the function to choose a plausible default output format.  If instead of -1, the values for the two parameters were 22 and 1, the output string value would be:


/**** InfoLog window, when parameters are 22 and 1
Message (11:47:24 am)


num2Str differences in CIL


For num2str the CIL difference is that the -1 value is invalid for the third parameter, the parameter that is named ‘decimals’ . Attempts to use -1 for the decimals parameter result in a System.ArgumentOutOfRangeException.


How to test in .NET CIL


If you are curious and want to run the num2str function in CIL mode, you can do so by using the Global::runClassMethodIL method, which is documented here.


The following code is the text of an .XPO file for a small test class. You could paste the code into your own .XPO file, and then import the .XPO into your test system. Then by running its Main method in the MorphX code editor, you can run num2str in CIL mode.


However, after import, and after each time you make any code change to this class, you must incrementally recompile the CIL.  You do this by opening an AOT window, right-clicking the root “AOT” node, and then clicking Add-Ins > Incremental CIL generation from X++.




Exportfile for AOT version 1.0 or later
Formatversion: 1

***Element: CLS

; Microsoft Dynamics AX Class: GmClass3Num2str unloaded
; --------------------------------------------------------------------------------

  CLASS #GmClass3Num2str
      Name #GmClass3Num2str
      Origin #{3FC77E58-CE3A-4B5C-8595-7EB9E12446CD}

      SOURCE #Method_Test_num2Str_as_CIL
        #public static server container Method_Test_num2Str_as_CIL
        # (container _conInputParam)
        # container conStringReturn;
        # str moneyString;
        # real moneyReal;
        # moneyReal = conPeek(_conInputParam, 1);
        # moneyString = num2str
        # (
        # moneyReal,
        # -1, // 'character': Minimun number of characters to output into the string.
        # 1, // 'decimals': Required number of digits to the right of the decimal.
        # DecimalSeparator::Dot, // 1
        # ThousandSeparator::Comma // 2
        # );
        # conStringReturn = [moneyString];
        # return conStringReturn;
      SOURCE #Main
        #static public server void Main(Args _args)
        # str retStr_Interp = "(Initial value.)";
        # str retStr_CIL = "retStr_CIL";
        # container containerOfInputParam;
        # container containerOfOutputReturnValue;
        # info("In Main, num2Str() run in X++ p-code Interpreted mode.");
        # containerOfInputParam = [12345.67];
        # containerOfOutputReturnValue = GmClass3Num2str
        # ::Method_Test_num2Str_as_CIL(containerOfInputParam);
        # retStr_Interp = conPeek(containerOfOutputReturnValue, 1);
        # info( retStr_Interp );
        # info("------------------------");
        # info("In Main, num2Str() run in .NET CIL mode.");
        # new XppILExecutePermission().assert();
        # containerOfInputParam = [32345.67];
        # containerOfOutputReturnValue =
        # Global::runClassMethodIL
        # (
        # "GmClass3Num2str",
        # "Method_Test_num2Str_as_CIL",
        # containerOfInputParam
        # );
        # retStr_CIL = conPeek(containerOfOutputReturnValue, 1);
        # info( retStr_CIL );
      SOURCE #classDeclaration
        #public class GmClass3Num2str

***Element: END


And here is the InfoLog content at the end of the run:


/**** InfoLog window, from run of .XPO
Message (12:41:31 pm)
In Main, num2Str() run in X++ p-code Interpreted mode.
In Main, num2Str() run in .NET CIL mode.

NOTE: This blog post refers to Microsoft Dynamics AX 2012, which was made generally available in 2011/August. The CIL difference described herein might be eliminated in a future release, and -1 might become valid for the ‘decimals’ parameter in CIL mode.