1# -*- coding: utf-8 -*-
2
3from math import log10, floor
4
5from ..units import html_of_unit, latex_of_unit, unicode_of_unit, to_unitless, unit_of
6from ..util.parsing import _unicode_sup
7
8
9def roman(num):
10    """
11    Examples
12    --------
13    >>> roman(4)
14    'IV'
15    >>> roman(17)
16    'XVII'
17
18    """
19    tokens = "M CM D CD C XC L XL X IX V IV I".split()
20    values = 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1
21    result = ""
22    for t, v in zip(tokens, values):
23        cnt = num // v
24        result += t * cnt
25        num -= v * cnt
26    return result
27
28
29def _mag(num):
30    return int(floor(log10(abs(num))))
31
32
33def _float_str_w_uncert(x, xe, precision=2):
34    """Prints uncertain number with parenthesis
35
36    Parameters
37    ----------
38    x : nominal value
39    xe : uncertainty
40    precision : number of significant digits in uncertainty
41
42    Examples
43    --------
44    >>> _float_str_w_uncert(-9.99752e5, 349, 3)
45    '-999752(349)'
46    >>> _float_str_w_uncert(-9.99752e15, 349e10, 2)
47    '-9.9975(35)e15'
48    >>> _float_str_w_uncert(3.1416, 0.029, 1)
49    '3.14(3)'
50    >>> _float_str_w_uncert(3.1416e9, 2.9e6, 1)
51    '3.142(3)e9'
52
53    Returns
54    -------
55    shortest string representation of "x +- xe" either as
56    ``x.xx(ee)e+xx`` or ``xxx.xx(ee)``
57
58    Notes
59    -----
60    The code in this function is from a question on StackOverflow:
61        http://stackoverflow.com/questions/6671053
62        written by:
63            Lemming, http://stackoverflow.com/users/841562/lemming
64        the code is licensed under 'CC-WIKI'.
65        (see: http://blog.stackoverflow.com/2009/06/attribution-required/)
66
67    """
68    # base 10 exponents
69    x_exp = int(floor(log10(abs(x))))
70    xe_exp = int(floor(log10(abs(xe))))
71
72    # uncertainty
73    un_exp = xe_exp - precision + 1
74    un_int = round(xe * 10 ** (-un_exp))
75
76    # nominal value
77    no_exp = un_exp
78    no_int = round(x * 10 ** (-no_exp))
79
80    # format - nom(unc)exp
81    fieldw = x_exp - no_exp
82    fmt = "%%.%df" % fieldw
83    result1 = (fmt + "(%.0f)e%d") % (no_int * 10 ** (-fieldw), un_int, x_exp)
84
85    # format - nom(unc)
86    fieldw = max(0, -no_exp)
87    fmt = "%%.%df" % fieldw
88    result2 = (fmt + "(%.0f)") % (no_int * 10 ** no_exp, un_int * 10 ** max(0, un_exp))
89
90    # return shortest representation
91    if len(result2) <= len(result1):
92        return result2
93    else:
94        return result1
95
96
97def _number_to_X(number, uncertainty, unit, fmt, unit_fmt, fmt_pow_10, space=" "):
98    uncertainty = uncertainty or getattr(number, "uncertainty", None)
99    unit = unit or unit_of(number)
100    integer_one = 1
101    if unit is integer_one:
102        unit_str = ""
103        mag = number
104    else:
105        unit_str = space + unit_fmt(unit)
106        mag = to_unitless(number, unit)
107        if uncertainty is not None:
108            uncertainty = to_unitless(uncertainty, unit)
109
110    if uncertainty is None:
111        if fmt is None:
112            fmt = 5
113        if isinstance(fmt, int):
114            flt = ("%%.%dg" % fmt) % mag
115        else:
116            flt = fmt(mag)
117    else:
118        if fmt is None:
119            fmt = 2
120        if isinstance(fmt, int):
121            flt = _float_str_w_uncert(mag, uncertainty, fmt)
122        else:
123            flt = fmt(mag, uncertainty)
124    if "e" in flt:
125        significand, mantissa = flt.split("e")
126        return fmt_pow_10(significand, mantissa) + unit_str
127    else:
128        return flt + unit_str
129
130
131def _latex_pow_10(significand, mantissa):
132    if significand in ("1", "1.0"):
133        fmt = "10^{%s}"
134    else:
135        fmt = significand + r"\cdot 10^{%s}"
136    return fmt % str(int(mantissa))
137
138
139def number_to_scientific_latex(number, uncertainty=None, unit=None, fmt=None):
140    r"""Formats a number as LaTeX (optionally with unit/uncertainty)
141
142    Parameters
143    ----------
144    number : float (w or w/o unit)
145    uncertainty : same as number
146    unit : unit
147    fmt : int or callable
148
149    Examples
150    --------
151    >>> number_to_scientific_latex(3.14) == '3.14'
152    True
153    >>> number_to_scientific_latex(3.14159265e-7)
154    '3.1416\\cdot 10^{-7}'
155    >>> import quantities as pq
156    >>> number_to_scientific_latex(2**0.5 * pq.m / pq.s)
157    '1.4142\\,\\mathrm{\\frac{m}{s}}'
158    >>> number_to_scientific_latex(1.23456, .789, fmt=2)
159    '1.23(79)'
160
161    """
162    return _number_to_X(
163        number, uncertainty, unit, fmt, latex_of_unit, _latex_pow_10, r"\,"
164    )
165
166
167def _unicode_pow_10(significand, mantissa):
168    if significand in ("1", "1.0"):
169        result = u"10"
170    else:
171        result = significand + u"·10"
172    return result + u"".join(map(_unicode_sup.get, str(int(mantissa))))
173
174
175def number_to_scientific_unicode(number, uncertainty=None, unit=None, fmt=None):
176    u"""Formats a number as unicode (optionally with unit/uncertainty)
177
178    Parameters
179    ----------
180    number : float (w or w/o unit)
181    uncertainty : same as number
182    unit : unit
183    fmt : int or callable
184
185    Examples
186    --------
187    >>> number_to_scientific_unicode(3.14) == u'3.14'
188    True
189    >>> number_to_scientific_unicode(3.14159265e-7) == u'3.1416·10⁻⁷'
190    True
191    >>> import quantities as pq
192    >>> number_to_scientific_unicode(2**0.5 * pq.m / pq.s)
193    '1.4142 m/s'
194
195    """
196    return _number_to_X(
197        number, uncertainty, unit, fmt, unicode_of_unit, _unicode_pow_10
198    )
199
200
201def _html_pow_10(significand, mantissa):
202    if significand in ("1", "1.0"):
203        result = "10<sup>"
204    else:
205        result = significand + "&sdot;10<sup>"
206    return result + str(int(mantissa)) + "</sup>"
207
208
209def number_to_scientific_html(number, uncertainty=None, unit=None, fmt=None):
210    r"""Formats a number as HTML (optionally with unit/uncertainty)
211
212    Parameters
213    ----------
214    number : float (w or w/o unit)
215    uncertainty : same as number
216    unit : unit
217    fmt : int or callable
218
219    Examples
220    --------
221    >>> number_to_scientific_html(3.14) == '3.14'
222    True
223    >>> number_to_scientific_html(3.14159265e-7)
224    '3.1416&sdot;10<sup>-7</sup>'
225    >>> number_to_scientific_html(1e13)
226    '10<sup>13</sup>'
227    >>> import quantities as pq
228    >>> number_to_scientific_html(2**0.5 * pq.m / pq.s)
229    '1.4142 m/s'
230
231    """
232    return _number_to_X(number, uncertainty, unit, fmt, html_of_unit, _html_pow_10)
233