| | 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): "⅔", (1, 5): "⅕", |
| | 110 | (2, 5): "⅖", (3, 5): "⅗", (4, 5): "⅘", |
| | 111 | (1, 6): "⅙", (5, 6): "⅚", (1, 8): "⅛", |
| | 112 | (3, 8): "⅜", (5, 8): "⅝", (7, 8): "⅞"} |
| | 113 | |
| | 114 | def html_fraction (number, maxdenom=10, useUnicode=True): |
| | 115 | """ |
| | 116 | Convert a float to a common fraction (or an integer if it is closer). |
| | 117 | |
| | 118 | If the output is a fraction, the fraction part is wrapped in a span |
| | 119 | with class "fraction" to enable styling of fractions. |
| | 120 | |
| | 121 | If useUnicode is true, unicode entities will be used where available. |
| | 122 | """ |
| | 123 | |
| | 124 | number = float(number) |
| | 125 | frac = _to_frac(abs(number), maxdenom) |
| | 126 | |
| | 127 | if type(frac) == int: |
| | 128 | string = str(frac) |
| | 129 | else: |
| | 130 | intpart, numerator, denominator = frac |
| | 131 | if useUnicode and (numerator, denominator) in _frac_entities: |
| | 132 | fracpart = _frac_entities[(numerator, denominator)] |
| | 133 | else: |
| | 134 | fracpart = (('<span class="fraction"><sup>%i</sup>' + |
| | 135 | '⁄<sub>%i</sub></span>') % |
| | 136 | (numerator,denominator)) |
| | 137 | if intpart == 0: |
| | 138 | string = fracpart |
| | 139 | else: |
| | 140 | string = str(intpart) + fracpart |
| | 141 | |
| | 142 | if number < 0: |
| | 143 | return '-'+string |
| | 144 | else: |
| | 145 | return string |
| | 146 | register.filter(html_fraction) |
| | 147 | |
| | 148 | def text_fraction (number, maxdenom=10): |
| | 149 | """Convert a float to a common fraction (or integer if it is closer).""" |
| | 150 | |
| | 151 | number = float(number) |
| | 152 | frac = _to_frac(abs(number), maxdenom) |
| | 153 | |
| | 154 | if type(frac) == int: |
| | 155 | string = str(frac) |
| | 156 | else: |
| | 157 | intpart, numerator, denominator = frac |
| | 158 | if intpart == 0: |
| | 159 | string = '%i/%i' % frac[1:] |
| | 160 | else: |
| | 161 | string = '%i %i/%i' % frac |
| | 162 | |
| | 163 | if number < 0: |
| | 164 | return '-'+string |
| | 165 | else: |
| | 166 | return string |
| | 167 | register.filter(text_fraction) |