This method takes a mixed-units value as a sequence of numbers, and returns a list of strings containing those numbers formatted in the way described in the specification.
# - - - M i x e d U n i t s . f o r m a t
def format ( self, coeffs, decimals=0, lz=False ):
"""Format mixed units.
[ (coeffs is a sequence of numbers as returned by
MixedUnits.singleToMix()) and
(decimals is a nonnegative integer) and
(lz is a bool) ->
return a list of strings corresponding to the values
of coeffs, with all the values but the last formatted
as integers, all values zero padded iff lz is true,
and the last value with (decimals) digits after the
decimal point ]
"""
The important feature of this method is to prevent
ever returning a last string which is greater than equal
to self.factors[-1]. For example, in
the degrees-minutes-seconds system, if the seconds value
is 59.9999, we want to avoid ever showing a value of
"60", "60.0", or anything
like that.
We'll implement this constraint by truncating any extra
digits, so 59.999 will display as "59",
"59.9", "59.99", and so
forth for the various values of decimals.
For the decimals=0 case, if the fractional
part of the last value is 0.5 or greater, formatting with
"%.0f" will round the value undesirably;
in that case, we subtract 0.5 from the last value to
effectively truncate instead of rounding.
For decimals=1, if the fractional part is
0.95 or greater, formatting with "%.1f"
will round. In that case, we subtract 0.05 from
the last value.
Progressing toward a general case, here is a table
showing the first few values of decimals,
the range of fraction values that will cause rounding,
and the fudge factor to be subtracted from the last value
to prevent rounding.
decimals | Rounding occurs | Fudge factor |
|---|---|---|
| 0 | >= 0.5 | 0.5 |
| 1 | >= 0.95 | 0.05 |
| 2 | >= 0.995 | 0.005 |
| 3 | >= 0.9995 | 0.0005 |
Generalizing, whenever the fractional part of the last
value in the coeffs sequence is greater
than or equal to 1-0.5×10-decimals, we must subtract 0.5×10-decimals from the last value.
First, we set coeffList to a copy of coeffs, right-padded with zero elements to standard
length. Then we use a list comprehension to format all the
values but the last as integers.
#-- 1 --
coeffList = self.__pad ( coeffs )
#-- 2 --
# [ result := the values from coeffList[:-1] formatted
# as integers ]
if lz: fmt = "%02d"
else: fmt = "%d"
result = [ fmt % x
for x in coeffList[:-1] ]
Next we separate the last value into whole and fractional
parts, and set fuzz to the quantity
0.5×10-decimals.
#-- 2 --
# [ whole := whole part of coeffList[-1]
# frac := fractional part of coeffList[-1]
# fuzz := 0.5 * (10 ** (-decimals) ]
whole, frac = divmod ( float(coeffList[-1]), 1.0 )
fuzz = 0.5 * (10.0 ** (-decimals))
Now apply the test and adjust if necessary.
#-- 3 --
# [ if frac >= (1-fuzz) ->
# result +:= [whole+frac-fuzz], formatted with
# (decimals) digits after the decimal
# else ->
# result += coeffList[-1], formatted with (decimals)
# digits after the decimal ]
if frac >= (1.0-fuzz):
corrected = whole + frac - fuzz
else:
corrected = coeffList[-1]
Next, the formatting. There are three cases.
If there are no left zeroes, we just use a format of the
form "%.,
where Df" is
Ddecimals.
If the caller wants left zeroes, the format has the form
"%0, where N.Df"N is
decimals plus three—two for the
digits to the left of the decimal, and one for the
decimal.
For left zeroes in the case of decimals=0, the format code is "%0, but in that case N.Df" is one less, because there will be no decimal
point.
N
#-- 4 --
# [ if lz ->
# s := corrected, formatted with 2 digits of left-zero
# padding and (decimals) precision
# else ->
# s := corrected, formatted with (decimals) precision ]
if lz:
if decimals: n = decimals+3
else: n = decimals+2
s = "%0*.*f" % (n, decimals, corrected)
else:
s = "%.*f" % (decimals, corrected)
#-- 5 --
result.append ( s )
#-- 6 --
return result