@@ -68,8 +68,76 @@ def _hash_algorithm(numerator, denominator):
68
68
\s*\Z # and optional whitespace to finish
69
69
""" , re .VERBOSE | re .IGNORECASE )
70
70
71
- # Pattern for matching format specification; only supports 'e', 'E', 'f', 'F'
72
- # and '%' presentation types.
71
+
72
+ # Helpers for formatting
73
+
74
+ def _round_to_exponent (n , d , exponent , no_neg_zero = False ):
75
+ """Round a rational number to an integer multiple of a power of 10.
76
+
77
+ Rounds the rational number n/d to the nearest integer multiple of
78
+ 10**exponent using the round-ties-to-even rule, and returns a
79
+ pair (sign, significand) representing the rounded value
80
+ (-1)**sign * significand.
81
+
82
+ d must be positive, but n and d need not be relatively prime.
83
+
84
+ If no_neg_zero is true, then the returned sign will always be False
85
+ for a zero result. Otherwise, the sign is based on the sign of the input.
86
+ """
87
+ if exponent >= 0 :
88
+ d *= 10 ** exponent
89
+ else :
90
+ n *= 10 ** - exponent
91
+
92
+ # The divmod quotient rounds ties towards positive infinity; we then adjust
93
+ # as needed for round-ties-to-even behaviour.
94
+ q , r = divmod (n + (d >> 1 ), d )
95
+ if r == 0 and d & 1 == 0 : # Tie
96
+ q &= - 2
97
+
98
+ sign = q < 0 if no_neg_zero else n < 0
99
+ return sign , abs (q )
100
+
101
+
102
+ def _round_to_figures (n , d , figures ):
103
+ """Round a rational number to a given number of significant figures.
104
+
105
+ Rounds the rational number n/d to the given number of significant figures
106
+ using the round-ties-to-even rule, and returns a triple (sign, significand,
107
+ exponent) representing the rounded value (-1)**sign * significand *
108
+ 10**exponent.
109
+
110
+ d must be positive, but n and d need not be relatively prime.
111
+ figures must be positive.
112
+
113
+ In the special case where n = 0, returns an exponent of 1 - figures, for
114
+ compatibility with formatting; the significand will be zero. Otherwise,
115
+ the significand satisfies 10**(figures - 1) <= significand < 10**figures.
116
+ """
117
+ # Find integer m satisfying 10**(m - 1) <= abs(self) <= 10**m if self
118
+ # is nonzero, with m = 1 if self = 0. (The latter choice is a little
119
+ # arbitrary, but gives the "right" results when formatting zero.)
120
+ if n == 0 :
121
+ m = 1
122
+ else :
123
+ str_n , str_d = str (abs (n )), str (d )
124
+ m = len (str_n ) - len (str_d ) + (str_d <= str_n )
125
+
126
+ # Round to a multiple of 10**(m - figures). The result will satisfy either
127
+ # significand == 0 or 10**(figures - 1) <= significand <= 10**figures.
128
+ exponent = m - figures
129
+ sign , significand = _round_to_exponent (n , d , exponent )
130
+
131
+ # Adjust in the case where significand == 10**figures.
132
+ if len (str (significand )) == figures + 1 :
133
+ significand //= 10
134
+ exponent += 1
135
+
136
+ return sign , significand , exponent
137
+
138
+
139
+ # Pattern for matching format specification; supports 'e', 'E', 'f', 'F',
140
+ # 'g', 'G' and '%' presentation types.
73
141
_FORMAT_SPECIFICATION_MATCHER = re .compile (r"""
74
142
(?:
75
143
(?P<fill>.)?
@@ -78,8 +146,8 @@ def _hash_algorithm(numerator, denominator):
78
146
(?P<sign>[-+ ]?)
79
147
(?P<no_neg_zero>z)?
80
148
(?P<alt>\#)?
81
- (?P<zeropad>0(?=\d))?
82
- (?P<minimumwidth>\d+)?
149
+ (?P<zeropad>0(?=\d))? # use lookahead so that an isolated '0' is treated
150
+ (?P<minimumwidth>\d+)? # as minimum width rather than the zeropad flag
83
151
(?P<thousands_sep>[,_])?
84
152
(?:\.(?P<precision>\d+))?
85
153
(?P<presentation_type>[efg%])
@@ -327,35 +395,6 @@ def __str__(self):
327
395
else :
328
396
return '%s/%s' % (self ._numerator , self ._denominator )
329
397
330
- def _round_to_sig_figs (self , figures ):
331
- """Round a positive fraction to a given number of significant figures.
332
-
333
- Returns a pair (significand, exponent) of integers such that
334
- significand * 10**exponent gives a rounded approximation to self, and
335
- significand lies in the range 10**(figures - 1) <= significand <
336
- 10**figures.
337
- """
338
- if not (self > 0 and figures > 0 ):
339
- raise ValueError ("Expected self and figures to be positive" )
340
-
341
- # Find integer m satisfying 10**(m - 1) <= self <= 10**m.
342
- str_n , str_d = str (self .numerator ), str (self .denominator )
343
- m = len (str_n ) - len (str_d ) + (str_d <= str_n )
344
-
345
- # Find best approximation significand * 10**exponent to self, with
346
- # 10**(figures - 1) <= significand <= 10**figures.
347
- exponent = m - figures
348
- significand = round (
349
- self / 10 ** exponent if exponent >= 0 else self * 10 ** - exponent
350
- )
351
-
352
- # Adjust in the case where significand == 10**figures.
353
- if len (str (significand )) == figures + 1 :
354
- significand //= 10
355
- exponent += 1
356
-
357
- return significand , exponent
358
-
359
398
def __format__ (self , format_spec , / ):
360
399
"""Format this fraction according to the given format specification."""
361
400
@@ -377,66 +416,61 @@ def __format__(self, format_spec, /):
377
416
f"for object of type { type (self ).__name__ !r} ; "
378
417
"can't use explicit alignment when zero-padding"
379
418
)
380
-
381
419
fill = match ["fill" ] or " "
382
420
align = match ["align" ] or ">"
383
421
pos_sign = "" if match ["sign" ] == "-" else match ["sign" ]
384
- neg_zero_ok = not match ["no_neg_zero" ]
422
+ no_neg_zero = bool ( match ["no_neg_zero" ])
385
423
alternate_form = bool (match ["alt" ])
386
424
zeropad = bool (match ["zeropad" ])
387
425
minimumwidth = int (match ["minimumwidth" ] or "0" )
388
426
thousands_sep = match ["thousands_sep" ]
389
427
precision = int (match ["precision" ] or "6" )
390
428
presentation_type = match ["presentation_type" ]
391
429
trim_zeros = presentation_type in "gG" and not alternate_form
392
- trim_dot = not alternate_form
430
+ trim_point = not alternate_form
393
431
exponent_indicator = "E" if presentation_type in "EFG" else "e"
394
432
395
- # Record sign, then work with absolute value.
396
- negative = self < 0
397
- self = abs (self )
398
-
399
- # Round to get the digits we need; also compute the suffix.
400
- if presentation_type == "f" or presentation_type == "F" :
401
- significand = round (self * 10 ** precision )
402
- point_pos = precision
403
- suffix = ""
404
- elif presentation_type == "%" :
405
- significand = round (self * 10 ** (precision + 2 ))
433
+ # Round to get the digits we need, figure out where to place the point,
434
+ # and decide whether to use scientific notation.
435
+ n , d = self ._numerator , self ._denominator
436
+ if presentation_type in "fF%" :
437
+ exponent = - precision - (2 if presentation_type == "%" else 0 )
438
+ negative , significand = _round_to_exponent (
439
+ n , d , exponent , no_neg_zero )
440
+ scientific = False
406
441
point_pos = precision
442
+ else : # presentation_type in "eEgG"
443
+ figures = (
444
+ max (precision , 1 )
445
+ if presentation_type in "gG"
446
+ else precision + 1
447
+ )
448
+ negative , significand , exponent = _round_to_figures (n , d , figures )
449
+ scientific = (
450
+ presentation_type in "eE"
451
+ or exponent > 0 or exponent + figures <= - 4
452
+ )
453
+ point_pos = figures - 1 if scientific else - exponent
454
+
455
+ # Get the suffix - the part following the digits.
456
+ if presentation_type == "%" :
407
457
suffix = "%"
408
- elif presentation_type in "eEgG" :
409
- if presentation_type in "gG" :
410
- figures = max (precision , 1 )
411
- else :
412
- figures = precision + 1
413
- if self :
414
- significand , exponent = self ._round_to_sig_figs (figures )
415
- else :
416
- significand , exponent = 0 , 1 - figures
417
- if presentation_type in "gG" and - 4 - figures < exponent <= 0 :
418
- point_pos = - exponent
419
- suffix = ""
420
- else :
421
- point_pos = figures - 1
422
- suffix = f"{ exponent_indicator } { exponent + point_pos :+03d} "
458
+ elif scientific :
459
+ suffix = f"{ exponent_indicator } { exponent + point_pos :+03d} "
423
460
else :
424
- # It shouldn't be possible to get here.
425
- raise ValueError (
426
- f"unknown presentation type { presentation_type !r} "
427
- )
461
+ suffix = ""
428
462
429
463
# Assemble the output: before padding, it has the form
430
464
# f"{sign}{leading}{trailing}", where `leading` includes thousands
431
465
# separators if necessary, and `trailing` includes the decimal
432
466
# separator where appropriate.
433
467
digits = f"{ significand :0{point_pos + 1 }d} "
434
- sign = "-" if negative and ( significand or neg_zero_ok ) else pos_sign
435
- leading = digits [:len (digits ) - point_pos ]
436
- frac_part = digits [len (digits ) - point_pos :]
468
+ sign = "-" if negative else pos_sign
469
+ leading = digits [: len (digits ) - point_pos ]
470
+ frac_part = digits [len (digits ) - point_pos :]
437
471
if trim_zeros :
438
472
frac_part = frac_part .rstrip ("0" )
439
- separator = "" if trim_dot and not frac_part else "."
473
+ separator = "" if trim_point and not frac_part else "."
440
474
trailing = separator + frac_part + suffix
441
475
442
476
# Do zero padding if required.
@@ -452,19 +486,19 @@ def __format__(self, format_spec, /):
452
486
if thousands_sep :
453
487
first_pos = 1 + (len (leading ) - 1 ) % 3
454
488
leading = leading [:first_pos ] + "" .join (
455
- thousands_sep + leading [pos : pos + 3 ]
489
+ thousands_sep + leading [pos : pos + 3 ]
456
490
for pos in range (first_pos , len (leading ), 3 )
457
491
)
458
492
459
- # Pad if necessary and return.
493
+ # Pad with fill character if necessary and return.
460
494
body = leading + trailing
461
495
padding = fill * (minimumwidth - len (sign ) - len (body ))
462
496
if align == ">" :
463
497
return padding + sign + body
464
498
elif align == "<" :
465
499
return sign + body + padding
466
500
elif align == "^" :
467
- half = len (padding )// 2
501
+ half = len (padding ) // 2
468
502
return padding [:half ] + sign + body + padding [half :]
469
503
else : # align == "="
470
504
return sign + padding + body
0 commit comments