Exegesis 7
by Damian Conway
|
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
Editor's note: this document is out of date and remains here for historic interest. See Synopsis 7 for the current design information.
Command our present numbers be muster'd...
A field specifier of the form {>>>>.<<} or {]]]].[[}
represents a decimal-aligned numeric field. The decimal marker always
appears in exactly the position indicated and the rest of the number is
aligned around it. The decimal places are rounded to the specific number
of places indicated, but only "significant" digits are shown. For example:
@nums = (1, 1.2, 1.23, 11.234, 111.235, 1.0001);
print form "Thy score be: {]]]].[[}",
@nums;
prints:
Thy score be: 1.0
Thy score be: 1.2
Thy score be: 1.23
Thy score be: 11.234
Thy score be: 111.235
Thy score be: 1.000
The points are all aligned, the minimal number of decimal places are
shown, and the decimals are rounded (using the same rounding protocol that
printf employs). Note in particular that, even though both 1 and
1.0001 would normally convert to the same 3-decimal-place value
(1.000), a form call only shows all three zeros in the second case
since only in the second case are they "significant".
In other words, unless we tell it otherwise,
form tries to avoid displaying a
number with more accuracy than it actually possesses (within the
constraint that it must always show at least one decimal place).
Here are only numbers ratified.
You're probably wondering what happens if we try to format a number that's too
large for the available places (as 123456.78 would be in the above format).
Whereas sprintf would extend a numeric field to accommodate the number,
form insists on preserving the specified layout; in particular, the
position of the decimal point. But it obviously can't just cut off the
extra high-order digits; that would change the value:
Thy score be: 23456.78
So, instead, it indicates that the number doesn't fit by filling the field with octothorpes (the way many spreadsheets do):
Thy score be: #####.###
Note, however, that it is possible to change this behaviour should we need to.
It's also possible that someone (not you, of course!) might attempt to pass a numeric field some data that isn't numeric at all:
my @mixed_data = (1, 2, "three", {4=>5}, "6", "7-Up");
print form 'Thy score be: {]]]].[[}',
@mixed_data;
Unlike Perl itself, form doesn't autoconvert non-numeric values.
Instead it marks them with another special string, by filling the field with
question-marks:
Thy score be: 1.0
Thy score be: 2.0
Thy score be: ?????.???
Thy score be: ?????.???
Thy score be: 6.0
Thy score be: ?????.???
Note that strings per se aren't a problem – form will happily
convert strings that contain valid numbers, such as "6" in the above
example. But it does reject strings that contain anything else besides
a number (even when Perl itself would successfully convert the number
– as it would for "7-Up" above).
Those who'd prefer Perl's usual, more laissez-faire attitude to
numerical conversion can just pre-numerify the values
themselves using the unary numerification operator (shown here in its
list form – +« – since we have an array of
values to be numerified):
print form 'Thy score be: {]]]].[[}',
+« @mixed_data;
This version would print:
Thy score be: 1.0
Thy score be: 2.0
Thy score be: 0.0
Thy score be: 1.0
Thy score be: 6.0
Thy score be: 7.0
(The 1.0 on the fourth line appears because Perl 6 hashes numerify to the
number of entries they contain).
See how the giddy multitude do point...
Of course, not everyone uses a dot for their decimal point. The other main
contender is the comma, and naturally form supports that as well. If
we specify a numeric field with a comma between the brackets:
@les_nums = (1, 1.2, 1.23, 11.234, 111.235, 1.0001);
print form 'Votre score est: {]]]],[[}',
@les_nums;
the call prints:
Votre score est: 1,0
Votre score est: 1,2
Votre score est: 1,23
Votre score est: 11,234
Votre score est: 111,235
Votre score est: 1,000
In fact, form is extremely flexible about the characters
we're allowed to use as
a decimal marker: anything except an angle- or square bracket or
a plus sign is acceptable.
As a bonus, form allows us to use the specified decimal marker in
the data as well as in the format. So this works too:
@les_nums = ("1", "1,2", "1,23", "11,234", "111,235", "1,0001");
print form 'Vos score est: {]]]],[[}',
@les_nums;
Or else be impudently negative...
Negative numbers work as expected, with the minus sign taking up one column of the field's allotted span:
@nums = ( 1, -1.2, 1.23, -11.234, 111.235, -12345.67);
print form 'Thy score be: {]]]].[[}',
@nums;
This would print:
Thy score be: 1.0
Thy score be: -1.2
Thy score be: 1.23
Thy score be: -11.234
Thy score be: 111.235
Thy score be: #####.###
However, form can also format numbers so that the minus sign trails the
number. To do that we simple put an explicit minus sign inside the field
specification, at the end:
print form 'Thy score be: {]]]].[[-}',
@nums;
which would then print:
Thy score be: 1.0
Thy score be: 1.2-
Thy score be: 1.23
Thy score be: 11.234-
Thy score be: 111.235
Thy score be: 12345.67-
form also understands the common financial usage where negative
numbers are represented as positive numbers in parentheses. Once again,
we draw an abstract picture of what we want (by putting parens at either
end of the field specification):
print form 'Thy dividend be: {(]]]].[[)}',
@nums;
and form obliges:
Thy dividend be: 1.0
Thy dividend be: (1.2)
Thy dividend be: 1.23
Thy dividend be: (11.234)
Thy dividend be: 111.235
Thy dividend be: (12345.67)
Note that the parens have to go inside the field's braces. Otherwise, they're just literal parts of the format string:
print form 'Thy dividend be: ({]]]].[[})',
@nums;
and we'd get:
Thy dividend be: ( 1.0 )
Thy dividend be: ( -1.2 )
Thy dividend be: ( 1.23 )
Thy dividend be: ( -11.234)
Thy dividend be: ( 111.235)
Thy dividend be: (#####.###)
And stand a comma 'tween their amities...
If we add so-called "thousands separators" inside a numeric field at the
usual places, form includes them appropriately in its output. It can
handle the five major formatting conventions:
my @nums = (0, 1, 1.1, 1.23, 4567.89, 34567.89, 234567.89, 1234567.89);
print form
"Brittannic Continental Subcontinental Tyrolean Asiatic",
"_____________ _____________ ______________ _____________ _____________",
"{],]]],]]].[} {].]]].]]],[} {]],]],]]].[} {]']]]']]],[} {]]]],]]]].[}",
@nums, @nums, @nums, @nums, @nums;
to produce:
Brittannic Continental Subcontinental Tyrolean Asiatic
_____________ _____________ ______________ _____________ _____________
0.0 0,0 0.0 0,0 0.0
1.0 1,0 1.0 1,0 1.0
1.1 1,1 1.1 1,1 1.1
1.23 1,23 1.23 1,23 1.23
4,567.89 4.567,89 4,567.89 4'567,89 4567.89
34,567.89 34.567,89 34,567.89 34'567,89 3,4567.89
234,567.89 234.567,89 2,34,567.89 234'567,89 23,4567.89
1,234,567.89 1.234.567,89 12,34,567.89 1'234'567,89 123,4567.89
It also accepts a space character as a "thousands separator" (with, of course, any decimal marker we might like):
print form
"Hyperspatial",
"_____________",
"{] ]]] ]]]:[}",
@nums;
to produce:
Hyperspatial
_____________
0:0
1:0
1:1
1:23
4 567:89
34 567:89
234 567:89
1 234 567:89
And gives to airy nothing a local habitation and a name
Of course, sometimes we don't know ahead of time just where in the world our
formatted numbers will be displayed. Locales were invented to address that
very problem, and form supports them.
If we use the :locale option, form detects the current locale and
converts any numerical formats it finds to the appropriate layout. For
example, if we wrote:
@nums = ( 1, -1.2, 1.23, -11.234, 111.235, -12345.67);
print form
"{],]]],]]].[[}",
@nums;
then we'd get:
1.0
-1.2
1.23
-11.234
111.235
-12,345.67
wherever the program was run. But if we had written:
print form
:locale,
"{],]]],]]].[[}",
@nums;
then we'd get:
1.0
-1.2
1.23
-11.234
111.235
-12,345.67
or:
1,0
1,2-
1,23
11,23-
111,235
12.345,67-
or:
1,0
(1,2)
1,23
(11,23)
111,235
(12'345,67)
or whatever else the current locale indicated was the correct local layout for numbers.
That is, when the :locale option is specified, form ignores the actual
decimal point, thousands separator, and negation sign we specified in the call,
and instead uses the values for these markers that are returned by the
POSIX localeconv function. That means that we can specify our numerical
formatting in a style that seems natural to us, and at the same time
allow the numbers to be formatted in a style that seems natural to the user.

