Formatted printing (D5.1 and D6)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Formatted printing (D5.1 and D6)

Peter Kenny-2
Hello all

I have been struggling to produce neatly formatted numerical output where
the figures line up in columns. I even feel nostalgic for the Fortran FORMAT
statement, which I never expected to say! Following a search for 'format' in
methods, I found the String>>sprintf: methods, which lead to the CRT
Library. Here we have the methods:
CRTLibrary>>_snprintf:count:format:with:
CRTLibrary>>_snprintf:count:format:with:with:
both of which have the comment line:
" see _snprintf:count:format:with:with:with: for further information."
I can't find any reference to this method. Could anyone tell me where to
start looking, please?
(And shouldn't the comment be updated?)

Thanks and best wishes

Peter Kenny


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Ian Bartholomew-21
Peter,

> I can't find any reference to this method. Could anyone tell me where to
> start looking, please?

Have a look at the docs for the c++ implementation at
http://www.cplusplus.com/ref/cstdio/sprintf.html  I don't think all the
options will work from Dolphin though.

If you are happy using a single fixed font for your document then it's
easy enough.

n := 1.
Transcript view font: (Font name: 'Courier' pointSize: 12).
[n < 10000000] whileTrue: [
        Transcript nextPutAll: ('%10d' sprintfWith: n); cr.
        n := n * 10]

If you want variable fonts, or want to mix fonts, then it gets a bit
more difficult.  Let us know what you are trying to format, and where
you are trying to format it, and we might be able to help.

--
Ian

Use the Reply-To address to contact me (limited validity).
Mail sent to the From address is ignored.


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Peter Kenny-2
Ian

> Have a look at the docs for the c++ implementation at
> http://www.cplusplus.com/ref/cstdio/sprintf.html  I don't think all the
> options will work from Dolphin though.


>  Let us know what you are trying to format, and where you are trying to
> format it, and we might be able to help.
>

Thanks for the quick reply. I have followed the link and tried to use the
options I want, but it does not seem to work as I expected. I am not after
anything fancy. I have a table with three columns of floats, and I want to
print each column in an 8 character field with 4 decimal places. As a test I
did:
'%8.4f' sprintfWith: 1.2345
and I got the answer '  0.0000'. When I changed the '%8.4f' to '%8.4e', it
gave the answer '7.8367e-316', and it gave the same answer if I dropped the
decimal point in the argument and put 12345.

At this point I feel inclined to give up and accept the default output from
Dolphin, even though I can't get the points to line up!

Thanks

Peter


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Schwab,Wilhelm K
Peter,

> Thanks for the quick reply. I have followed the link and tried to use the
> options I want, but it does not seem to work as I expected. I am not after
> anything fancy. I have a table with three columns of floats, and I want to
> print each column in an 8 character field with 4 decimal places. As a test I
> did:
> '%8.4f' sprintfWith: 1.2345
> and I got the answer '  0.0000'. When I changed the '%8.4f' to '%8.4e', it
> gave the answer '7.8367e-316', and it gave the same answer if I dropped the
> decimal point in the argument and put 12345.
>
> At this point I feel inclined to give up and accept the default output from
> Dolphin, even though I can't get the points to line up!

Have you considered #printOn:decimalPlaces:?  Getting the points to line
up correctly might be even more trouble than simply getting the right
number of digits on the right of the decimal point, at least in a
variable pitch font.  One way to do it would be to draw the decimal
places and then draw the digits relative to them.  Of course, if you
care about alignment, then a fixed pitch font is probably better anyway.

Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Ian Bartholomew-21
In reply to this post by Peter Kenny-2
Peter,

 >'%8.4f' sprintfWith: 1.2345 and I got the answer '  0.0000'.

I think Dolphin floats are one of the things that sprintf won't work
with.  The following does work but, again, you need a fixed font.

r := Random new.
canvas := Transcript view canvas.
rows := OrderedCollection new.
10 timesRepeat: [
        rows add: (OrderedCollection new
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                yourself)].
Transcript view font: (Font name: 'Courier' pointSize: 12).
rows do: [:each |
        1 to: 3 do: [:index |
                s := (each at: index) printString.
                left := s copyFrom: 1 to: (s indexOf: $.) - 1.
                right := s copyFrom: (s indexOf: $.).
                Transcript
                        nextPutAll: ('%10s' sprintfWith: left);
                        nextPutAll: right;
                        nextPutAll: ((String new: 5 - right size) atAllPut: $ )].
        Transcript cr]

> At this point I feel inclined to give up and accept the default output from
> Dolphin, even though I can't get the points to line up!

The latest RichEdit control has a tab mode that enables you to line up
decimal points.  My RichEdit goody exposes this functionality and _I
think_ it would be possible to port that back to the Dolphin
RichTextEdit class.  It would mean that you have to send your output to
a RichText document rather than a simple string.  If you are interested
I can have a go at porting it back - from a quick glance, it should only
need the addition of a structure and a few interface methods.


On a slightly different tack I've had a play at using loose
(proportional font) spaces to format a table i.e. you work out how many
pixels the text will take and then the number of spaces needed to pad
out each side of the string to make it fit into a specific field size.

NOTE...
- It's not perfect by any means.  Because a proportional space still has
a finite width you end up with a closest fit approach which,
unavoidably, leaves a little wobble in the vertical alignment.
- This code is a result of workspace experimentation.  With a bit of
thought I'm sure it could be refactored to make it a _lot_ less ugly.
- The following code treats each of the fields in a row as a separate
entity, so the alignment of the first column is better than that of the
third.  If you treated each _row_ as a separate entity you could
probably get better alignment by building the string a column at a time
(I might have a go at that when time permits because it's got me
wondering how good it would be).

r := Random new.
canvas := Transcript view canvas.
rows := OrderedCollection new.
10 timesRepeat: [
        rows add: (OrderedCollection new
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                yourself)].

decimalWidth := 150. "pixels from lhs to decimal point"
fieldWidth := 200. "pixels in full field width"

rows do: [:each |
        1 to: 3 do: [:index |
                n := each at: index.
                integerExtent := (canvas textExtent: n truncated printString) x.
                gapNeeded := decimalWidth - integerExtent.
                spaces := 2.
                [(canvas textExtent: ((String new: spaces) atAllPut: $ )) x <
gapNeeded] whileTrue: [spaces := spaces + 1].
                tooSmall := (canvas textExtent: ((String new: spaces - 1) atAllPut: $
)) x.
                tooBig := (canvas textExtent: ((String new: spaces) atAllPut: $ )) x.
                closestSpaces := (gapNeeded - tooSmall) < (tooBig - gapNeeded)
                        ifTrue: [spaces - 1]
                        ifFalse: [spaces].
                s := ((String new: closestSpaces) atAllPut: $ ) , n printString.

                fullExtent := (canvas textExtent: s) x.
                gapNeeded := fieldWidth - fullExtent.
                spaces := 2.
                [(canvas textExtent: ((String new: spaces) atAllPut: $ )) x <
gapNeeded] whileTrue: [spaces := spaces + 1].
                tooSmall := (canvas textExtent: ((String new: spaces - 1) atAllPut: $
)) x.
                tooBig := (canvas textExtent: ((String new: spaces) atAllPut: $ )) x.
                closestSpaces := (gapNeeded - tooSmall) < (tooBig - gapNeeded)
                        ifTrue: [spaces - 1]
                        ifFalse: [spaces].
                s := s , ((String new: closestSpaces) atAllPut: $ ).
                Transcript nextPutAll: s].
        Transcript cr]

--
Ian

Use the Reply-To address to contact me (limited validity).
Mail sent to the From address is ignored.


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Chris Uppal-3
In reply to this post by Peter Kenny-2
Peter.

> '%8.4f' sprintfWith: 1.2345

Floating point and 64-bit integers don't work with variadic external functions
such as the printf() family.

The reason is that these numbers are represented (to the external code) as
>32-bit quantities.  Normally Dolphin knows how to do the translation, but that
is only possible if Dolphin knows that a translation is /required/.  That will
be the case if the function is defined to take a 'double' argument (for
instance).  Unfortunately, the variadic functions rely on the native stack
format, plus some magic (in the external function itself) which knows how to
unravel the stack structure.  So if a C function calls:
    printf("%f", 3.14)
then the C compiler will (this is over-simplified) push a pointer to the format
string, and  64-bits worth of floating point data onto the stack.  The code
implementing printf() will /parse/ the format string to find out what else to
expect on the stack, and can get at the various arguments from that.  The
/only/ thing that tells it what the arguments are is the contents of the format
string -- if that's wrong then Nasty Things Will Happen...   Now Dolphin can't
parse the string (well, it /could/, but that would require special-case code
for every variadic function), so it doesn't know what structure to build on the
stack.  So it compromises by just converting everything to a 32-bit form, and
pushing that.  That works fine for <=32 bit integers, characters, strings, and
whatnot.  It /doesn't/ work for floats.

FWIW, I have a package that lets you talk to a wider range of variadic external
functions, which includes (as an example application)
String>>sprintfWithArguments: which will format floating-point (and other)
data.  E.g:
    '%010.4f' sprintfWithArguments: #( 3.14159265358979  )
See the package 'CU Varargs' under Miscellanea if you're interested.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Ian Bartholomew-21
In reply to this post by Ian Bartholomew-21
I wrote:

> - The following code treats each of the fields in a row as a separate
> entity, so the alignment of the first column is better than that of the
> third.  If you treated each _row_ as a separate entity you could
> probably get better alignment by building the string a column at a time
> (I might have a go at that when time permits because it's got me
> wondering how good it would be).

The answer is "not really good enough".  See workspace code below.

So, if you are OK with using a fixed font then Chris' enhanced sprintf
is the way to go.

If you want to used a variable width font then you will either have to
use a RichEdit or accept a small amount of slop in the formatting.

If you are just displaying on the screen (you don't want to save or
print the data) then you can always write directly, and accurately, to
the canvas.



"sloppy (both the result and the coding ;-) ) variable font version"

r := Random new.
canvas := Transcript view canvas.
rows := OrderedCollection new.
10 timesRepeat: [
        rows add: (OrderedCollection new
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                add: ((r next * (r next * 1000)) roundTo: 0.0001);
                yourself)].
offset := 100.

rows do: [:each |
        "print integer side of col 1"
        sFirst := canvas textExtent: each first truncated printString.
        this := String new.
        count := 1.
        [ last := this copy.
                this := ((String new: count) atAllPut: $ ) , each second truncated
printString.
                (canvas textExtent: this) x <= offset] whileTrue: [count := count + 1].
        tooSmall := (canvas textExtent: last) x.
        tooBig := (canvas textExtent: this) x.
        s := ((offset - tooSmall) < (tooBig - offset)
                ifTrue: [last copy]
                ifFalse: [this copy]).

        "print a  dp, the fractional part and the integer part of col2"
        sFirst := each first printString.
        this := String new.
        count := 1.
        [ last := this copy.
                this := (sFirst copyFrom: (sFirst indexOf: $.)) , ((String new: count)
atAllPut: $ ) , each second truncated printString.
                (canvas textExtent: this) x <= offset] whileTrue: [count := count + 1].
        tooSmall := (canvas textExtent: last) x.
        tooBig := (canvas textExtent: this) x.
        s := s , ((offset - tooSmall) < (tooBig - offset)
                ifTrue: [last]
                ifFalse: [this]).

        "print a  dp, the fractional part and the integer part of col3"
        sFirst := each second printString.
        this := String new.
        count := 1.
        [ last := this copy.
                this := (sFirst copyFrom: (sFirst indexOf: $.)) , ((String new: count)
atAllPut: $ ) , each third truncated printString.
                (canvas textExtent: this) x <= offset] whileTrue: [count := count + 1].
        tooSmall := (canvas textExtent: last) x.
        tooBig := (canvas textExtent: this) x.
        s := s , ((offset - tooSmall) < (tooBig - offset)
                ifTrue: [last]
                ifFalse: [this]).

        "add thefractional part of the third column"
        sFirst := each third printString.
        s := s , (sFirst copyFrom: (sFirst indexOf: $.)).

        Transcript nextPutAll: s; cr]

--
Ian

Use the Reply-To address to contact me (limited validity).
Mail sent to the From address is ignored.


Reply | Threaded
Open this post in threaded view
|

Re: Formatted printing (D5.1 and D6)

Peter Kenny-2
In reply to this post by Chris Uppal-3
Bill, Ian, Chris

I should have known that when I asked this group I would get a complete
answer - or even three! Many thanks for all your suggestions. I am quite
happy to use a fixed width font, so Chris's version of sprintf will meet my
needs.

Thanks again

Peter