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 |
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. |
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 |
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] |
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. |
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 |
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. |
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 |
Free forum by Nabble | Edit this page |