| 70 | |
| 71 | def _to_frac(x, maxdenom=10): |
| 72 | """ |
| 73 | Convert x to a common fraction. |
| 74 | |
| 75 | Chooses the closest fraction to x with denominator <= maxdenom. |
| 76 | If x is closest to an integer, return that integer; otherwise, |
| 77 | return an (integer, numerator, denominator) tuple. |
| 78 | """ |
| 79 | |
| 80 | assert x >= 0, "_to_frac only works on positive numbers." |
| 81 | |
| 82 | intpart = int(x) |
| 83 | x -= intpart |
| 84 | |
| 85 | bestfrac = 0,1 |
| 86 | mindiff = x |
| 87 | |
| 88 | for denom in range(1,maxdenom+1): |
| 89 | # for each denominator, there are two numerators to consider: |
| 90 | # the one below x and the one above x |
| 91 | for num in (int(x*denom), int(x*denom+1)): |
| 92 | diff = abs(float(num)/denom - x) |
| 93 | |
| 94 | # compare using '<' rather than '<=' to ensure that the |
| 95 | # fraction with the smallest denominator is preferred |
| 96 | if diff < mindiff: |
| 97 | bestfrac = num, denom |
| 98 | mindiff = diff |
| 99 | |
| 100 | if bestfrac[0] == 0: |
| 101 | return intpart |
| 102 | elif mindiff >= 1-x: |
| 103 | return intpart+1 |
| 104 | else: |
| 105 | return intpart, bestfrac[0], bestfrac[1] |
| 106 | |
| 107 | |
| 108 | _frac_entities = {(1,4): "¼", (1,2): "½", (3,4): "¾", |
| 109 | (1, 3): "⅓", (2, 3): "⅔", |
| 110 | # browser support for fifths and sixths is incomplete, so we don't use them by default |
| 111 | # (1, 5): "⅕", (2, 5): "⅖", (3, 5): "⅗", (4, 5): "⅘", |
| 112 | # (1, 6): "⅙", (5, 6): "⅚", |
| 113 | (1, 8): "⅛", (3, 8): "⅜", (5, 8): "⅝", (7, 8): "⅞"} |
| 114 | |
| 115 | def html_fraction (number, maxdenom=10, useUnicode=True): |
| 116 | """ |
| 117 | Convert a float to a common fraction (or an integer if it is closer). |
| 118 | |
| 119 | If the output is a fraction, the fraction part is wrapped in a span |
| 120 | with class "fraction" to enable styling of fractions. |
| 121 | |
| 122 | If useUnicode is true, unicode entities will be used where available. |
| 123 | """ |
| 124 | |
| 125 | number = float(number) |
| 126 | frac = _to_frac(abs(number), maxdenom) |
| 127 | |
| 128 | if type(frac) == int: |
| 129 | string = str(frac) |
| 130 | else: |
| 131 | intpart, numerator, denominator = frac |
| 132 | if useUnicode and (numerator, denominator) in _frac_entities: |
| 133 | fracpart = _frac_entities[(numerator, denominator)] |
| 134 | else: |
| 135 | fracpart = (('<span class="fraction">' + |
| 136 | '<span class="numerator">%i</span>⁄' + |
| 137 | '<span class="denominator">%i</span></span>') % |
| 138 | (numerator,denominator)) |
| 139 | if intpart == 0: |
| 140 | string = fracpart |
| 141 | else: |
| 142 | string = str(intpart) + fracpart |
| 143 | |
| 144 | if number < 0: |
| 145 | return '-'+string |
| 146 | else: |
| 147 | return string |
| 148 | register.filter(html_fraction) |
| 149 | |
| 150 | def text_fraction (number, maxdenom=10): |
| 151 | """Convert a float to a common fraction (or integer if it is closer).""" |
| 152 | |
| 153 | number = float(number) |
| 154 | frac = _to_frac(abs(number), maxdenom) |
| 155 | |
| 156 | if type(frac) == int: |
| 157 | string = str(frac) |
| 158 | else: |
| 159 | intpart, numerator, denominator = frac |
| 160 | if intpart == 0: |
| 161 | string = '%i/%i' % frac[1:] |
| 162 | else: |
| 163 | string = '%i %i/%i' % frac |
| 164 | |
| 165 | if number < 0: |
| 166 | return '-'+string |
| 167 | else: |
| 168 | return string |
| 169 | register.filter(text_fraction) |