Click here to Skip to main content
Click here to Skip to main content

A printf implementation in C#

By , 26 Jan 2009
 

Screenshot - printf.jpg

Introduction

While working on a project porting a large C/C++ Unix application into the .NET world, the language requirements changed from mixed C#/VB.NET/Managed C++ to C# (and only C#). In the C/C++ sources of this project, there were many [sf]printf statements. Migrating these to the corresponding C# String.Format format is not only annoying, but also a little problematic. This is because String.Format does not support all the required possibilities, as printf does. So, what to do? The solution was to implement a printf equivalent in C#, and that's what I will present to you in this article.

Using the code

Place a reference to the Tools assembly of the demo project, or incorporate my code into your source. Call Tools.printf with the appropriate parameters, and you are done. printf features a lot of conversion and formatting options -- far more than String.Format does -- but this comes at the price that it's also more complex, in case you have never used printf. To find out more about printf and its possibilities, have a look at the printf man page or just Google for man printf.

It is important to note that not all possible printf options, conversions, and formats are supported at the moment. There is also more than one printf implementation available on the 'net! This implementation supports the most common options. It supports the l and h length modifiers, all flags such as #+- ', and the following formats: iudxXfFeEgGon. There is no difference between ASCII and Unicode strings and characters; they are always Unicode.

printf outputs to the console. It also has variations like sprinf, which returns a formatted string, and fprinf, which writes formatted output to a stream. It supports variable length parameters. If there are more format placeholders than actual value parameters, then placeholders without a value will be removed from the result.

// Ouput: Account balance:       +12.345.678,00 (great)

Tools.printf("Account balance: %'+20.2f (%s)\n", 12345678, "great");

Building and testing the demo project

The demo project uses NUnit with test cases to test some printf features, but not all combinations of these. So, you will need NUnit installed.

Points of interest

Converting C/C++ code with lots of [sf]printf statements into C# is now a little easier. This printf implementation uses Regex \%([\'\#\-\+ ]*)(\d*)(?:\.(\d+))?([hl])?([dioxXucsfeEgGpn%]) to parse the format string and uses String.Format internally wherever possible. Strings are processed by hand only if not otherwise possible, so the code is quite simple and small.

Some people have pointed out that it is possible to use either the underlying unmanaged Windows API functions like:

[DllImport("msvcrt.dll",  SetLastError = false, 
  CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sprintf(StringBuilder buff, string format, __arglist);
...
StringBuilder sb = new StringBuilder(256);
DateTime dt = DateTime.Now;
int hr = dt.Hour;
int mn = dt.Minute;
int sc = dt.Second;
int ml = dt.Millisecond;
MSVCRT.sprintf(sb, "%02i:%02i:%02i.%03i", __arglist(hr, mn, sc, ml));
string s = sb.ToString();
...

or a managed C++ wrapper called from C#:

namespace FormatHelper
{
    void FormatHelper::FormatDouble(String ^%sResult, String ^sFormat, double fVal)
    {
        CString sStr;
        sStr.Format (CString(sFormat), fVal);
        sResult = gcnew String(sStr);
    }
}

Call it like this from C#:

using FormatHelper;

string s = string.Empty;
Class1.FormatDouble(ref s, "%f6.1",132.459);

Both solutions will work, but sometimes it's not possible to use unmanaged code and other languages because of various reasons (Management - you know? ;-). That's because this is a complete rewrite of printf in C# without any dependencies.

Update

Thanks to Rainer Helbing, this printf implementation now also supports parameter indices in the form of "%[parameterIndex][flags][width][.precision][length]type". A detailed description can be found here.

The NUnit test routines are also modified to work as expected with all Country settings for decimal and group separators.

A bug when specifying a precision with hex format was solved.

History

  • 2007.06.14 -- First release.
  • 2009.01.26 -- Update.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Richard Prinz
Architect
Austria Austria
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionscanfmembermail-2212 Apr '12 - 2:03 
how about scanf ?
 
with respect
GeneralMy vote of 5memberwinxpdll8 Sep '11 - 20:32 
Excellent!
Thanks
GeneralMy vote of 5memberfn567 Feb '11 - 4:35 
Excellent!
Need to update according to post by danie_lidstrom.
Thanks
QuestionHow about performance?memberlizaxs8 Mar '10 - 21:55 
First of all, congratulations on a very fine article and an idea that indeed will help a lot of conversions go smoother.
I'm only a bit worried about performance since you are using RegEx. I definitely don't want to get into the argument of
which syntax is better as this is just a personal opinion, mostly coming from background. On the other hand, I do believe
that you should inform the readers of this article that when using large amounts of calls to your printf function(s), a
performance degradation is at hand, as RegEx is not only slow in C#, it also generates quite a lot of overhead.
 
Apart from that you got my 5, as I believe good initiatives for code migration should always be supported...
GeneralA couple of bugsmemberst144 Jun '09 - 16:57 
In FormatNumber():
 
1. The CLR is not happy when the format string is "{0:d0}" and the value object is a double.
 
Fix:
 

if (numberFormat.Contains("d") && Value is double)
{
double num = (double)Value;
w = String.Format(numberFormat, (int)num);
}
else
w = String.Format(numberFormat, Value);
 

 
2. The padding if numbers does not take the size of the length of the value object into account.
 

if (FieldLength != int.MinValue && w.Length < FieldLength - 1)
w = w.PadLeft( FieldLength - 1, Padding );
if ( IsPositive( Value, true ) )
w = ( PositiveSign ?
"+" : ( PositiveSpace ?
" " : (FieldLength != int.MinValue && w.Length < FieldLength ?
Padding.ToString() : String.Empty ) ) ) + w;


AnswerRe: A couple of bugs [modified]memberdanie_lidstrom30 Sep '10 - 4:28 
Actually this was my very first test. Something like this:
 
var s = Tools.sprintf("%02d", 7.6);
Console.WriteLine(s);
This will throw System.FormatException: Format specifier was invalid. I can agree that the format is invalid, and C printf will print garbage given such a formatting. However it would be nice if the function didn't throw any exceptions at all, favoring robustness over correctness.
 
The second problem mentioned is when you are formatting something like this:
 
var s = Tools.sprintf("%02d", 7777);
Console.WriteLine(s);
 
The original implementation will print "07777" which is wrong. The C printf prints "7777" which is the correct output.
 
Here is the FormatNumber function with the applied fixes:
 
        #region FormatNumber
        private static string FormatNumber(string NativeFormat, bool Alternate,
                                           int FieldLength, int FieldPrecision,
                                           bool Left2Right,
                                           bool PositiveSign, bool PositiveSpace,
                                           char Padding, object Value)
        {
            string w = String.Empty;
            string lengthFormat = "{0" + (FieldLength != int.MinValue ?
                                            "," + (Left2Right ?
                                                    "-" :
                                                    String.Empty) + FieldLength.ToString() :
                                            String.Empty) + "}";
            string numberFormat = "{0:" + NativeFormat + (FieldPrecision != int.MinValue ?
                                            FieldPrecision.ToString() :
                                            "0") + "}";
 
            if (IsNumericType(Value))
            {
                if (numberFormat.Contains("d") && Value is double)
                {
                    double num = (double)Value;
                    w = String.Format(numberFormat, (int)num);
                }
                else
                {
                    w = String.Format(numberFormat, Value);
                }
 
                if (Left2Right || Padding == ' ')
                {
                    if (IsPositive(Value, true))
                        w = (PositiveSign ?
                                "+" : (PositiveSpace ? " " : String.Empty)) + w;
                    w = String.Format(lengthFormat, w);
                }
                else
                {
                    if (w.StartsWith("-"))
                        w = w.Substring(1);
                    if (FieldLength != int.MinValue)
                        w = w.PadLeft(FieldLength - 1, Padding);
                    if (IsPositive(Value, true))
                        w = (PositiveSign ?
                                "+" : (PositiveSpace ?
                                        " " : (FieldLength != int.MinValue && w.Length < FieldLength ?
                                                Padding.ToString() : String.Empty))) + w;
                    else
                        w = "-" + w;
                }
            }
 
            return w;
        }
	#endregion
 
Thanks for a great piece of work!

modified on Thursday, September 30, 2010 10:41 AM

GeneralThanks, Code I can rely on.memberst143 May '09 - 2:59 
Translating sprintf statements from C/C++ to C# is one of the worst programming jobs I have ever come across. Thanks, you've done a great job with this implementation !
 
Cheers
 
Stuart
GeneralAlthough...memberbobpombrio22 Apr '09 - 4:03 
This is exactly what I need. To those of you who think this is a waste - Where is the clear and consice documentation on Format in C#? I do not want to waste any more time trying to figure out Format, I've been writting C code since the first half of the 80's and yet Format kicks my but. Where as I know prinf like the back of my hand and it has always done exactly what I needed it to do. Also there is tons of documentation on its format specifiers, where is the docs on the C# Format?
 
Good job Richard!
 
Thanks again.Thumbs Up | :thumbsup:
AnswerRe: Although...memberRichard Prinz22 Apr '09 - 22:11 
Thanks for this comment and great that you find this article useful.
 
The documentation for type formating can be found at msdn:
 
http://msdn.microsoft.com/en-us/library/fbxft59x(VS.85).aspx[^]
 
Internally this printf implementation only translates printf formating to c#
formating.
 
brgds
 
Richard
GeneralMy vote of 1memberCPAV27 Jan '09 - 7:44 
simply wrong approach
GeneralAlternative using MSVCRT sprintfmemberjepo11 Dec '08 - 2:53 
Hi Richard,
 
I very much enjoyed finding your arcticle on The Code Project
about a sprintf-like formatter. Being a C/C++ developer at hart I
find the format specifiers in C# confusing and lacking in possibilities.
 
I have to point out that when formatting an integer value of say
22 with the format specifier "%02i" the result will be "022" which
is not what I expected.
 
I continued looking into the possibiliy of using the msvcrt sprintf
from C# and finally I stumbled upon the following:
 
[DllImport("msvcrt.dll", SetLastError = false, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sprintf(StringBuilder buff, string format, __arglist);
 
which one can use then in the following manner:
 
public static void FormatSomeNumbers()
{
StringBuilder sb = new StringBuilder(256);

DateTime dt = DateTime.Now;

int hr = dt.Hour;
int mn = dt.Minute;
int sc = dt.Second;
int ml = dt.Millisecond;

MSVCRT.sprintf(sb, "%02i:%02i:%02i.%03i", __arglist(hr, mn, sc, ml));

string s = sb.ToString();

short si = 0;
double d1 = 1;
double d2 = 2;
float f = 3;
long l = 12345;

MSVCRT.sprintf(sb, "%hi %lf %lf %f %f %I64i", __arglist(si, d1, d2, (double)f, (double)f, l));

s = sb.ToString();
}
 
You have to cast a float to double for this to work as
this is what sprintf seems to expect.
 
I thought you might find this useful.
 
Kind regards,
 
Jeroen Posch.
The Netherlands.
Questiona Bug?membereminsenay5 Jun '08 - 1:36 
Thank you for implementing this converter. It really helps me solving my problem. However, I think I found a bug in your sprintf code. My format string is "%.2x". In real printf, this formatting returns 05 for 5, for example. In your code the result is 5, without the leading 0. The source of the bug is that you call the FormatHEX function with int.MinValue as a FieldPrecision parameter. The problem is solved when I changed int.MinValue with fieldPrecision parameter of the sprintf function. However, if possible, I want to learn if there is a specific reason of calling FormatHEX and some of the other formatting functions with int.MinValue.
 
Thanks...
AnswerRe: a Bug?memberRichard Prinz26 Jan '09 - 5:06 
Thanks for this info. It was a Bug. An update to this article will be posted shortly.
GeneralHere is an alternate solutionmemberArthg17 Apr '08 - 10:55 
Create a C++/CLI assembly.
The following is just POC, could be generalized, I know.
 
namespace FormatHelper
{
 
void FormatHelper::FormatDouble(String ^%sResult, String ^sFormat, double fVal)
{
CString sStr;
sStr.Format (CString(sFormat), fVal);
sResult = gcnew String(sStr);
}
 
}
 
call like this:
 
using FormatHelper;
 
string s = string.Empty;
Class1.FormatDouble(ref s, "%f6.1",132.459);
 
Minimal code. POC works!
GeneralDubiousmemberwkempf20 Jun '07 - 9:46 
The argument of making it easier to "translate" is somewhat valid. It means you don't have to fully understand both formatting schemes and how to translate them. That might be useful to someone, I suppose.
 
But you're really trying to claim printf is more capable?!? I'm not aware of a single thing printf can do that String.Format can't. Conversely, there's a lot String.Format can do that printf can't.
AnswerRe: DubiousmemberRichard Prinz20 Jun '07 - 22:18 
Thank you for this comment!
 
I agree String.Format can do things printf can't. Like
 
String.Format(”{0:$#,##0.00;($#,##0.00);Zero}”, value);
 
which will output “$1,240.00 if passed 1243.50. It will
output the same format but in parentheses if the number is
negative, and will output the string “Zero” if the number is
zero. But specifying this format is ugly [ my personal opinion ].
 
On the other side printf("%#10o\n", 42) can't be done using
only String.Format [ and yes I know Convert.ToString( value, base )
to convert to octal or IFormatProvider, ICustomFormatter to create
my own formats ]
 
The problem was not understanding both formating schemes
[ as you falsely assume ], the problem was the need to convert
from one formating world to another which is very anoying if
you have hundreds of statements - believe me!
 


GeneralRe: Dubiousmemberwkempf21 Jun '07 - 2:47 
I didn't assume. You stated in the article that there were things printf could do that String.Format could not. That was really the ONLY issue I had. I don't think there's too many people that will be in your shoes, needing to convert a lot of printf statements to C#, so I'm not sure how useful a C# version of printf is in general. And remarks in the article that lead people who don't know any better to believe that printf is somehow more powerful will lead many to use this library when they shouldn't be.
 
Other than that, I don't have any issues with the implementation, or the article.
GeneralRe: DubiousmemberRichard Prinz21 Jun '07 - 4:40 
I think "people who don't know any better" (as you wrote) should have the
right to choose between String.Format and printf. Four you, a printf in C# might not
be usefull, for others it is.
GeneralRe: DubiousmemberMichael Lee Yohe25 Sep '07 - 5:03 
Thanks for [sf]*printf()!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 26 Jan 2009
Article Copyright 2007 by Richard Prinz
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid