Hi Michal, Joel,
I've had format.r up on rebol.org for a long time, hoping someone
would give me a good idea on how to improve it. Joel's ROUND is a
great idea. First, here's a version of ROUND that can handle values
where to-integer chokes, fractions less than one, and negative numbers:
round: func [n [number!] p [integer!] /local factor neg] [
if negative? n [n: (- n) neg: true]
factor: 10.0 ** p
n: n * factor + 0.5
n: n - (n // 1) / factor
either neg [- n][n]
]
Now I've made a new version of FORMAT, which uses the logic of ROUND and
produces a string representation in decimal format, pads with zeros, etc.
It also will format all the values in a block in one go. Note that REBOL
forms any value less than .1 into exponential notation, you have to use a
special formatting function to get it into decimal format. It's really
incredible that a language that prides itself on being readable to humans
doesn't have a standard way to do this.
>> pi / 100
== 3.14159265358979E-2
>> round pi / 100 5
== 3.142E-2
>> format pi / 100 5
== "0.03142"
Note that FORMAT has a refinement /full which won't work without the
function FULL-FORM. I've been working on this with Larry Palmiter and
Gerald Goertzel. It'll be available on rebol.org in a couple of days.
FULL-FORM is meant to give the simplest possible string representation
of a decimal value that will load back to exactly the same value.
>> form pi
== "3.14159265358979"
>> pi - load form pi
== 3.10862446895044E-15 ; not equal!
>> full-form pi
== "3.141592653589793"
>> pi - load full-form pi
== 0 ; equal!
Have fun,
Eric
===========
format: func [
{CONVERTS Rebol data into formatted strings.}
item "value or block of values to format - block values are reduced first"
decimal [integer! block! unset!]
{optional decimal places to leave (2 by default)}
width [integer! block! unset!]
{optional width of formatted string}
/full "use extra precision in forming decimal values"
/local n neg p factor exponent
;
; Examples:
;
;>> print format [pi exp 1]
;3.14 2.72
;>> print format [pi exp 1] 4 ; specifying precision
;3.1416 2.7183
;>> print format [pi exp 1][4 8] ; using different precisions
;3.1416 2.71828183
;>> print format [pi exp 1] 4 10 ; pad with spaces
; 3.1416 2.7183
;>> format pi * 1000 -2 ; round off to nearest hundred
;== "3100"
;>> print format pi 16
;3.1415926535897900 ; precision given by FORM
;>> print format/full pi 16
;3.1415926535897930 ; maximum precision
;>> print format exp 1 16
;2.7182818284590500
;>> print format/precise exp 1 16
;2.7182818284590452
;
; Note: DECIMAL must be specified before specifying WIDTH. Making DECIMAL and
; WIDTH optional is for convenience when entered at the prompt. Both should
; be specified within programs.
;
][
if not value? 'decimal [ decimal: 2 ] ; supply defaults if needed
if not value? 'width [ width: 0 ]
if object? item [ item: next second item ]
either any-block? item [
if integer? decimal [ decimal: reduce [decimal] ]
if integer? width [ width: reduce [width] ]
n: to block! reduce item ; won't change values in lists and hashes
while [not tail? n][
either any-block? first n [ ; recurse with all format values
change/only n format first n head decimal head width
][ ; use current format value
change/only n format first n first decimal first width
]
n: next n
; get next format values, or reuse the last one
if 1 < length? decimal [ decimal: next decimal ]
if 1 < length? width [ width: next width ]
]
][
if block? decimal [ decimal: first decimal ]
if block? width [ width: first width ]
if not number? decimal [ decimal: 2 ]
if not number? width [ width: 0 ]
either number? item [
if negative? item [item: (- item) neg: true]
factor: 10 ** decimal
item: item * factor + 0.5
item: item - (item // 1)
n: either full [full-form item][form item]
if p: find n "." [
if 2 <> index? p [make error! "FORMAT: funny decimal point"]
remove p
p: remove find n "E"
exponent: to integer! p
clear p
insert/dup tail n "0" exponent + 1 - length? n
]
if positive? decimal [
insert/dup n "0" 1 + decimal - length? n
insert skip tail n (- decimal) "."
]
if all [
negative? decimal
n <> "0"
][insert/dup tail n "0" (- decimal)]
if neg [insert n "-"]
][
n: form item
]
; pad with spaces
either width < 0 [
insert/dup tail n " " 0 - width - length? n
][
insert/dup n " " width - length? n
]
]
head n
]