All pastes #751062 Raw Edit

Y_Less

public text v1 · immutable
#751062 ·published 2007-10-27 00:48 UTC
rendered paste body
format, formatex, printf, printfex and Text_SendFormat

These functions all create a string from other data, i.e. numbers and other strings, the difference is that printf displays this generated string in the server console, format saves the generated string in another string, Text_SendFormat sends the string to players for display.  The ex versions support the extra YSI identifiers.  These functions all use the same method of determining the types of passed parameters: the format string.  An example of this is:

"Hello %s, the number is: %d"

% is the format identifer, it tells the system to do something special, not to display the character (to display a percent sign do: %%, all other characters not following a % and not related to it are displayed normally).

The default symbols are:

b - Binary value
c - Character
d - Integer
f - Floating point
h - Hex value
i - Integer
s - String
x - Hex value
% - Percent sign

YSI also supports a further set of symbols in it's text system:

o - Octal value
n - Command name
p - Numerical suffix
u - Unsigned variable base number
t - Signed variable base number

So the above format string, in a full format function would be:

format(string, sizeof (string), "Hello %s, the number is %d", "Y_Less", 5);

If you were to them read the contents of the variable string you would see:

"Hello Y_Less, the number is 5"

Why?

The format processor reads through the format string, copying letters over to the destination string until it reaches a %.  At this point it gets the type of format parameter, as determined by the next character and, using this type, gets the next parameter after the format string.  When it reaches the second % it gets the second parameter after the format string and so on (3rd gets the 3rd, 4th 4th etc) (Note: %% does not increase the paramter counter as it doesn't use a parameter so the second % would get the first parameter if it was after the %% for example).

If you use the wrong parameter type you will get very strange values and possibly even crashes.

Symbols after the letter (or %) after a % are not considered part of it so:

format(string, sizeof (string), "%sss", "aa");

Would give:

"ssaa"

As there are two s's which are just normal text as they're after the s after the %.

An example of a printf statement:

printf("The value of floatVar is %f", floatVar);

The word floatVar appears twice in that strng but they are not the same.  The first is in a string so will always read as floatVar but has no knowledge at all of the variable called floatVar (you could put: printf("The value of something is %f", floatVar); and it would do exactly the same thing (except for the name of the variable displayed, which is now "something")).  floatVar, as implied by it's name, holds a float (a non-whole number such as 3.5 or 7.0 (which is whole but in float format)), from the table above we can see that the format code for displaying a float is f so we have used %f.  The output of this line will look something like:

"The value of floatVar is 7.000000"

Now I don't know about you but I don't need to know the value of a number to the nearest millionth, it probably doesn't make much difference after a hundredth, possibly a thousandth for high accuracy things, so we can change this.  In a format string % starts it and the letter identifier ends it, this means you can add additional information if you need:

printf("The value of floatVar is %.2f", floatVar);

This line will give:

"The value of floatVar is 7.00"

Which is a much nicer number to use.  The %.2f means display a float number to 2 decimal places, %.4f would display it to 4 decimal places, %.0f would basically round it off to an integer, there would be no decimal part.

The rounding is called precision, the other paramter on floats is the width, this allows you to pad out strings to a length for display purposes, e.g:

printf("Line 1: %.2f %.2f %.2f", length[0], width[0], height[0]);
printf("Line 2: %.2f %.2f %.2f", length[1], width[1], height[1]);
printf("Line 3: %.2f %.2f %.2f", length[2], width[2], height[2]);

Now lets say length, width and height are:

new length[3] = {1.5,      798.12, 34.2   };
new  width[3] = {456.1293, 890.1,  439.565};
new height[3] = {2.0,      2.0,    2.0    };

These are all nicely lined up so you can see what goes together, however if you used the print statements above you would get:

"Line 1: 1.50 798.12 34.20"
"Line 2: 456.12 890.10 439.00"
"Line 3: 2.00 2.00 2.00"

It looks very very messy, you could adjust the spacing to line them up but you would need to adjust it for every different number.  This is where widths come in.  The longest number, including decimal point, is 6 characters in this example (remember they're trimmed to 2dp), this may be the highest they go or you may have longer numbers.  Either way you need to find the longest length or number you will have and set that as your width.  Incorporating this into the print statements gives us:

printf("Line 1: %6.2f %6.2f %6.2f", length[0], width[0], height[0]);
printf("Line 2: %6.2f %6.2f %6.2f", length[1], width[1], height[1]);
printf("Line 3: %6.2f %6.2f %6.2f", length[2], width[2], height[2]);

The maximum width goes before the decimal point in the float format identifier to identfy it as a width, not a precision (if you didn't have a set precision you would simply have %6f which is perfectly valid).  This gives:

"Line 1: 1.50   798.12 34.20 "
"Line 2: 456.12 890.10 439.00"
"Line 3: 2.00   2.00   2.00  "

Which is obviously better lined up.  Note that if you have a numebr longer than the max width it will show the whole number which may mess up your alignment.  Basically width pads the number to the desired width if required.

Integers can also be padded, and you have more control over it.  There are 3 options: length of padding, type of padding and location of padding.  Length of padding is much like the float, a format identifier of %4d would pad the number to 4 spaces, however unlike floats by default the padding comes BEFORE the number, not after.  The second option is type of padding, this can be either spaces, as is default, or zeros, identified by a leading 0 on the width (e.g. %04d).  The final option is location of padding, either before (by default) or after (by negating the info value), so %-4d would pad the number to 4 spaces with the padding coming after the number.  Examples:

printf("%d %i", 123, 7);      // "123 7"
printf("%2d %4i", 123, 7);    // "123    7"
printf("%4d %2i", 123, 7);    // " 123  7"
printf("%04d %-4i", 123, 7);  // "0123 7   "
printf("%-04d %04i", 123, 7); // "1230 0007"

The final thing with these basic identifer modifiers was added in SA:MP 0.2 after their inclusion in YSI and that's the * width and * precision.  This takes a paramter as the width or precision instead of a set number.  Note that you cannot set the location or type of the padding with *.  Example:

printf("%.2f", 89.0);        // "89.00"
printf("%.*f", 2, 89.0);     // "89.00"
printf("%.*f", 4, 89.0);     // "89.0000"
printf("%*.2f", 7, 89.0);    // "89.00  "
printf("%*.*f", 7, 3, 89.0); // "89.000 "
printf("%-0*d", 5, -12);     // "-1200"

Note that the - on the -12 is one of the 5 characters.

Obviously this is very useful as the width/precision parameter can be a variable and thus alterable dynamically without multiple formats to generate a format string.

Width and precision can also be used on strings, width is as you'd expect - it pads strings to a minimum length with spaces (these are after the string as normal).  Precision is a very useful but little known thing which allows you to define the length of string to take, this, combined with the fact that you can define the start point, means you can extract any portion of a string using only format.  If you didn't know you can define the start point of a string using string[start] instead of just string as the parameter, although this is technically a cell the function wants a string so it's more like a pointer.  Note: this doesn't apply to constant strings, only variables.

Examples:

printf("%s", "Hello there");  // "Hello there"

new str0[] = "Hello there";
printf("%s", str0); // "Hello there"

new str1[] = "Hello there";
printf("%s", str1[6]); // "there"

printf("%.5s", "Hello there"); // "Hello"

printf("%15s", "Hello there"); // "Hello there    "

printf("%15.5s", "Hello there"); // "Hello          "

printf("%.*s", 2, "Hello"); // "He"

new str2[] = "Hello";
printf("%.*s", 2, str2[3]); // "lo"

new str3[] = "Hello";
printf("%.*s", 3, str3[2]); // "llo"

new str4[] = "Select some words";
printf("%9.4s", str4[7]); // "some     "

new str5[] = "Select some words";
printf("%*.*s", 7, 5, str4[1]); // "elect  "

%c just gives a character, there are no options on it:

new str[] = "FEAR";
printf("%c", str[2]); // "A"
printf("%c", 65);     // "A"
printf("%c", 'A');    // "A"
printf("%c", '\65');  // "A"
printf("%c", '\x41'); // "A"

%b, %h and %x behave the same as %d and %i just give different bases.

The YSI text functions have a few extra format identifers for use when sending messages, currently these aren't supported in a new format function but should be in the near future.  First of these is %o, this is very similar to %b, %d, %h, %i and %x but in base 8 (aka octal), it's a mostly deprecated base but is still useful for bit shifting as:

data >> 010;

Shifts data right one byte.  Octal numbers tend to be written with a leading 0 to distingusish them (and to state that they are base 8 in programming) but %o does not apply this so you're best of writing: 0%o (as you are writing 0x%x and 0b%b).  %o has all the settings of the other numbers (zero/space padding) unfortunately this can't be used to apply the leading 0 as the number may be longer than the minimum length in which case the padding won't show up.  Example (using the YSI function printfex):

printfex("0%o", 64); // 0100
printfex("0%o", 7); // 07
printfex("0%o", 8); // 010
printfex("0%o", 9); // 011

The other two are optional base identifiers.  They have the same modifiers as all the other numbers but no set base, the base is decided as the precision (default 10 if none specified or base negative):

printfex("%.3t", 9); // 9 in signed base 3, output: 100
printfex("%.3t", -1); // -1 in signed base 3, output: -1
printfex("%.3t", 1); // 1 in signed base 3, output: 1
printfex("%.3t", 7); // 7 in signed base 3, output: 21

printfex("%.3u", 9); // 9 in unsigned base 3, output: 100
printfex("%.3u", -1); // -1 in unsigned base 3, output: 121121222121102021010
printfex("%.3u", 1); // 1 in unsigned base 3, output: 1
printfex("%.3u", 7); // 7 in unsigned base 3, output: 21

printfex("%.10u", -1); // -1 in unsigned base 10, output: 4294967295

printfex("%-0*.*t", 7, 36, -100); // -100 in signed base 36, right zero padded to 7 characters, output: -2S00000

%p simply adds a suffix for the number passed (NOT for any previous numbers), i.e. st, nd, rd or th:

printfex("%d%p", 3, 3); // 3rd
printfex("3%p", 7); // 3th
printfex("%p", 21); // st

Again, there are no parameters for this, but it is useful for things like:

printfex("You came %d%p", position, position); // You came 4th

The final one introduced in YSI is %n.  YSI has dynamic commands so doing:

printfex("/commands");

May be wrong as it may not be /commands, it may have been renamed, however "commands" is still it's internal identifier so we can use this:

printfex("/%n", "commands");

will now print whatever the commands command has been renamed (including prefix).  %n has the same options as %s, assuming "/commands" has been renamed "/ysi list" for example:

printfex("%n", "commands"); // "ysi list"
printfex("%.10n", "commands"); // "ysi list  "
printfex("%5n", "commands"); // "ysi l"