source: sasview/src/sas/sascalc/data_util/formatnum.py @ 25dd9c9

Last change on this file since 25dd9c9 was 9a5097c, checked in by andyfaff, 8 years ago

MAINT: import numpy as np

  • Property mode set to 100644
File size: 18.5 KB
Line 
1# This program is public domain
2# Author: Paul Kienzle
3"""
4Format values and uncertainties nicely for printing.
5
6:func:`format_uncertainty_pm` produces the expanded format v +/- err.
7
8:func:`format_uncertainty_compact` produces the compact format v(##),
9where the number in parenthesis is the uncertainty in the last two digits of v.
10
11:func:`format_uncertainty` uses the compact format by default, but this
12can be changed to use the expanded +/- format by setting
13format_uncertainty.compact to False.
14   
15The formatted string uses only the number of digits warranted by
16the uncertainty in the measurement.
17
18If the uncertainty is 0 or not otherwise provided, the simple
19%g floating point format option is used.
20
21Infinite and indefinite numbers are represented as inf and NaN.
22
23Example::
24
25    >>> v,dv = 757.2356,0.01032
26    >>> print format_uncertainty_pm(v,dv)
27    757.236 +/- 0.010
28    >>> print format_uncertainty_compact(v,dv)
29    757.236(10)
30    >>> print format_uncertainty(v,dv)
31    757.236(10)
32    >>> format_uncertainty.compact = False
33    >>> print format_uncertainty(v,dv)
34    757.236 +/- 0.010
35
36UncertaintyFormatter() returns a private formatter with its own
37formatter.compact flag.
38"""
39from __future__ import division
40
41import math
42import numpy as np
43__all__ = ['format_uncertainty', 'format_uncertainty_pm',
44           'format_uncertainty_compact']
45
46# Coordinating scales across a set of numbers is not supported.  For easy
47# comparison a set of numbers should be shown in the same scale.  One could
48# force this from the outside by adding scale parameter (either 10**n, n, or
49# a string representing the desired SI prefix) and having a separate routine
50# which computes the scale given a set of values.
51
52# Coordinating scales with units offers its own problems.  Again, the user
53# may want to force particular units.  This can be done by outside of the
54# formatting routines by scaling the numbers to the appropriate units then
55# forcing them to print with scale 10**0.  If this is a common operation,
56# however, it may want to happen inside.
57
58# The value e<n> is currently formatted into the number.  Alternatively this
59# scale factor could be returned so that the user can choose the appropriate
60# SI prefix when printing the units.  This gets tricky when talking about
61# composite units such as 2.3e-3 m**2 -> 2300 mm**2, and with volumes
62# such as 1 g/cm**3 -> 1 kg/L.
63
64
65def format_uncertainty_pm(value, uncertainty):
66    """
67    Given *value* v and *uncertainty* dv, return a string v +/- dv.
68    """
69    return _format_uncertainty(value, uncertainty, compact=False)
70
71
72def format_uncertainty_compact(value, uncertainty):
73    """
74    Given *value* v and *uncertainty* dv, return the compact
75    representation v(##), where ## are the first two digits of
76    the uncertainty.
77    """
78    return _format_uncertainty(value, uncertainty, compact=True)
79
80
81class UncertaintyFormatter:
82    """
83    Value and uncertainty formatter.
84
85    The *formatter* instance will use either the expanded v +/- dv form
86    or the compact v(##) form depending on whether *formatter.compact* is
87    True or False.  The default is True.
88    """
89    compact = True
90   
91    def __call__(self, value, uncertainty):
92        """
93        Given *value* and *uncertainty*, return a string representation.
94        """
95        return _format_uncertainty(value, uncertainty, self.compact)
96format_uncertainty = UncertaintyFormatter()
97
98
99def _format_uncertainty(value, uncertainty, compact):
100    """
101    Implementation of both the compact and the +/- formats.
102    """
103    # Handle indefinite value
104    if np.isinf(value):
105        return "inf" if value > 0 else "-inf"
106    if np.isnan(value):
107        return "NaN"
108
109    # Handle indefinite uncertainty
110    if uncertainty is None or uncertainty <= 0 or np.isnan(uncertainty):
111        return "%g" % value
112    if np.isinf(uncertainty):
113        if compact:
114            return "%.2g(inf)" % value
115        else:
116            return "%.2g +/- inf" % value
117
118    # Handle zero and negative values
119    sign = "-" if value < 0 else ""
120    value = abs(value)
121
122    # Determine scale of value and error
123    err_place = int(math.floor(math.log10(uncertainty)))
124    if value == 0:
125        val_place = err_place - 1
126    else:
127        val_place = int(math.floor(math.log10(value)))
128
129    if err_place > val_place:
130        # Degenerate case: error bigger than value
131        # The mantissa is 0.#(##)e#, 0.0#(##)e# or 0.00#(##)e#
132        val_place = err_place + 2
133    elif err_place == val_place:
134        # Degenerate case: error and value the same order of magnitude
135        # The value is ##(##)e#, #.#(##)e# or 0.##(##)e#
136        val_place = err_place + 1
137    elif err_place <= 1 and val_place >= -3:
138        # Normal case: nice numbers and errors
139        # The value is ###.###(##)
140        val_place = 0
141    else:
142        # Extreme cases: zeros before value or after error
143        # The value is ###.###(##)e#, ##.####(##)e# or #.#####(##)e#
144        pass
145   
146    # Force engineering notation, with exponent a multiple of 3
147    val_place = int(math.floor(val_place / 3.)) * 3
148
149    # Format the result
150    digits_after_decimal = abs(val_place - err_place + 1)
151    val_str = "%.*f" % (digits_after_decimal, value / 10.**val_place)
152    exp_str = "e%d" % val_place if val_place != 0 else ""
153    if compact:
154        err_str = "(%2d)" % int(uncertainty / 10.**(err_place - 1) + 0.5)
155        result = "".join((sign, val_str, err_str, exp_str))
156    else:
157        err_str = "%.*f" % (digits_after_decimal, uncertainty / 10.**val_place)
158        result = "".join((sign, val_str, exp_str + " +/- ", err_str, exp_str))
159    return result
160
161
162def test_compact():
163    # Oops... renamed function after writing tests
164    value_str = format_uncertainty_compact
165
166    # val_place > err_place
167    assert value_str(1235670,766000) == "1.24(77)e6"
168    assert value_str(123567.,76600) == "124(77)e3"
169    assert value_str(12356.7,7660) == "12.4(77)e3"
170    assert value_str(1235.67,766) == "1.24(77)e3"
171    assert value_str(123.567,76.6) == "124(77)"
172    assert value_str(12.3567,7.66) == "12.4(77)"
173    assert value_str(1.23567,.766) == "1.24(77)"
174    assert value_str(.123567,.0766) == "0.124(77)"
175    assert value_str(.0123567,.00766) == "0.0124(77)"
176    assert value_str(.00123567,.000766) == "0.00124(77)"
177    assert value_str(.000123567,.0000766) == "124(77)e-6"
178    assert value_str(.0000123567,.00000766) == "12.4(77)e-6"
179    assert value_str(.00000123567,.000000766) == "1.24(77)e-6"
180    assert value_str(.000000123567,.0000000766) == "124(77)e-9"
181    assert value_str(.00000123567,.0000000766) == "1.236(77)e-6"
182    assert value_str(.0000123567,.0000000766) == "12.357(77)e-6"
183    assert value_str(.000123567,.0000000766) == "123.567(77)e-6"
184    assert value_str(.00123567,.000000766) == "0.00123567(77)"
185    assert value_str(.0123567,.00000766) == "0.0123567(77)"
186    assert value_str(.123567,.0000766) == "0.123567(77)"
187    assert value_str(1.23567,.000766) == "1.23567(77)"
188    assert value_str(12.3567,.00766) == "12.3567(77)"
189    assert value_str(123.567,.0764) == "123.567(76)"
190    assert value_str(1235.67,.764) == "1235.67(76)"
191    assert value_str(12356.7,7.64) == "12356.7(76)"
192    assert value_str(123567,76.4) == "123567(76)"
193    assert value_str(1235670,764) == "1.23567(76)e6"
194    assert value_str(12356700,764) == "12.35670(76)e6"
195    assert value_str(123567000,764) == "123.56700(76)e6"
196    assert value_str(123567000,7640) == "123.5670(76)e6"
197    assert value_str(1235670000,76400) == "1.235670(76)e9"
198
199    # val_place == err_place
200    assert value_str(123567,764000) == "0.12(76)e6"
201    assert value_str(12356.7,76400) == "12(76)e3"
202    assert value_str(1235.67,7640) == "1.2(76)e3"
203    assert value_str(123.567,764) == "0.12(76)e3"
204    assert value_str(12.3567,76.4) == "12(76)"
205    assert value_str(1.23567,7.64) == "1.2(76)"
206    assert value_str(.123567,.764) == "0.12(76)"
207    assert value_str(.0123567,.0764) == "12(76)e-3"
208    assert value_str(.00123567,.00764) == "1.2(76)e-3"
209    assert value_str(.000123567,.000764) == "0.12(76)e-3"
210
211    # val_place == err_place-1
212    assert value_str(123567,7640000) == "0.1(76)e6"
213    assert value_str(12356.7,764000) == "0.01(76)e6"
214    assert value_str(1235.67,76400) == "0.001(76)e6"
215    assert value_str(123.567,7640) == "0.1(76)e3"
216    assert value_str(12.3567,764) == "0.01(76)e3"
217    assert value_str(1.23567,76.4) == "0.001(76)e3"
218    assert value_str(.123567,7.64) == "0.1(76)"
219    assert value_str(.0123567,.764) == "0.01(76)"
220    assert value_str(.00123567,.0764) == "0.001(76)"
221    assert value_str(.000123567,.00764) == "0.1(76)e-3"
222
223    # val_place == err_place-2
224    assert value_str(12356700,7640000000) == "0.0(76)e9"
225    assert value_str(1235670,764000000) == "0.00(76)e9"
226    assert value_str(123567,76400000) == "0.000(76)e9"
227    assert value_str(12356,7640000) == "0.0(76)e6"
228    assert value_str(1235,764000) == "0.00(76)e6"
229    assert value_str(123,76400) == "0.000(76)e6"
230    assert value_str(12,7640) == "0.0(76)e3"
231    assert value_str(1,764) == "0.00(76)e3"
232    assert value_str(0.1,76.4) == "0.000(76)e3"
233    assert value_str(0.01,7.64) == "0.0(76)"
234    assert value_str(0.001,0.764) == "0.00(76)"
235    assert value_str(0.0001,0.0764) == "0.000(76)"
236    assert value_str(0.00001,0.00764) == "0.0(76)e-3"
237
238    # val_place == err_place-3
239    assert value_str(12356700,76400000000) == "0.000(76)e12"
240    assert value_str(1235670,7640000000) == "0.0(76)e9"
241    assert value_str(123567,764000000) == "0.00(76)e9"
242    assert value_str(12356,76400000) == "0.000(76)e9"
243    assert value_str(1235,7640000) == "0.0(76)e6"
244    assert value_str(123,764000) == "0.00(76)e6"
245    assert value_str(12,76400) == "0.000(76)e6"
246    assert value_str(1,7640) == "0.0(76)e3"
247    assert value_str(0.1,764) == "0.00(76)e3"
248    assert value_str(0.01,76.4) == "0.000(76)e3"
249    assert value_str(0.001,7.64) == "0.0(76)"
250    assert value_str(0.0001,0.764) == "0.00(76)"
251    assert value_str(0.00001,0.0764) == "0.000(76)"
252    assert value_str(0.000001,0.00764) == "0.0(76)e-3"
253
254    # Zero values
255    assert value_str(0,7640000) == "0.0(76)e6"
256    assert value_str(0, 764000) == "0.00(76)e6"
257    assert value_str(0,  76400) == "0.000(76)e6"
258    assert value_str(0,   7640) == "0.0(76)e3"
259    assert value_str(0,    764) == "0.00(76)e3"
260    assert value_str(0,     76.4) == "0.000(76)e3"
261    assert value_str(0,      7.64) == "0.0(76)"
262    assert value_str(0,      0.764) == "0.00(76)"
263    assert value_str(0,      0.0764) == "0.000(76)"
264    assert value_str(0,      0.00764) == "0.0(76)e-3"
265    assert value_str(0,      0.000764) == "0.00(76)e-3"
266    assert value_str(0,      0.0000764) == "0.000(76)e-3"
267
268    # negative values
269    assert value_str(-1235670,765000) == "-1.24(77)e6"
270    assert value_str(-1.23567,.766) == "-1.24(77)"
271    assert value_str(-.00000123567,.0000000766) == "-1.236(77)e-6"
272    assert value_str(-12356.7,7.64) == "-12356.7(76)"
273    assert value_str(-123.567,764) == "-0.12(76)e3"
274    assert value_str(-1235.67,76400) == "-0.001(76)e6"
275    assert value_str(-.000123567,.00764) == "-0.1(76)e-3"
276    assert value_str(-12356,7640000) == "-0.0(76)e6"
277    assert value_str(-12,76400) == "-0.000(76)e6"
278    assert value_str(-0.0001,0.764) == "-0.00(76)"
279
280    # non-finite values
281    assert value_str(-np.inf,None) == "-inf"
282    assert value_str(np.inf,None) == "inf"
283    assert value_str(np.NaN,None) == "NaN"
284   
285    # bad or missing uncertainty
286    assert value_str(-1.23567,np.NaN) == "-1.23567"
287    assert value_str(-1.23567,-np.inf) == "-1.23567"
288    assert value_str(-1.23567,-0.1) == "-1.23567"
289    assert value_str(-1.23567,0) == "-1.23567"
290    assert value_str(-1.23567,None) == "-1.23567"
291    assert value_str(-1.23567,np.inf) == "-1.2(inf)"
292
293def test_pm():
294    # Oops... renamed function after writing tests
295    value_str = format_uncertainty_pm
296
297    # val_place > err_place
298    assert value_str(1235670,766000) == "1.24e6 +/- 0.77e6"
299    assert value_str(123567., 76600) == "124e3 +/- 77e3"
300    assert value_str(12356.7,  7660) == "12.4e3 +/- 7.7e3"
301    assert value_str(1235.67,   766) == "1.24e3 +/- 0.77e3"
302    assert value_str(123.567,    76.6) == "124 +/- 77"
303    assert value_str(12.3567,     7.66) == "12.4 +/- 7.7"
304    assert value_str(1.23567,      .766) == "1.24 +/- 0.77"
305    assert value_str(.123567,      .0766) == "0.124 +/- 0.077"
306    assert value_str(.0123567,     .00766) == "0.0124 +/- 0.0077"
307    assert value_str(.00123567,    .000766) == "0.00124 +/- 0.00077"
308    assert value_str(.000123567,   .0000766) == "124e-6 +/- 77e-6"
309    assert value_str(.0000123567,  .00000766) == "12.4e-6 +/- 7.7e-6"
310    assert value_str(.00000123567, .000000766) == "1.24e-6 +/- 0.77e-6"
311    assert value_str(.000000123567,.0000000766) == "124e-9 +/- 77e-9"
312    assert value_str(.00000123567, .0000000766) == "1.236e-6 +/- 0.077e-6"
313    assert value_str(.0000123567,  .0000000766) == "12.357e-6 +/- 0.077e-6"
314    assert value_str(.000123567,   .0000000766) == "123.567e-6 +/- 0.077e-6"
315    assert value_str(.00123567,    .000000766) == "0.00123567 +/- 0.00000077"
316    assert value_str(.0123567,     .00000766) == "0.0123567 +/- 0.0000077"
317    assert value_str(.123567,      .0000766) == "0.123567 +/- 0.000077"
318    assert value_str(1.23567,      .000766) == "1.23567 +/- 0.00077"
319    assert value_str(12.3567,      .00766) == "12.3567 +/- 0.0077"
320    assert value_str(123.567,      .0764) == "123.567 +/- 0.076"
321    assert value_str(1235.67,      .764) == "1235.67 +/- 0.76"
322    assert value_str(12356.7,     7.64) == "12356.7 +/- 7.6"
323    assert value_str(123567,     76.4) == "123567 +/- 76"
324    assert value_str(1235670,   764) == "1.23567e6 +/- 0.00076e6"
325    assert value_str(12356700,  764) == "12.35670e6 +/- 0.00076e6"
326    assert value_str(123567000, 764) == "123.56700e6 +/- 0.00076e6"
327    assert value_str(123567000,7640) == "123.5670e6 +/- 0.0076e6"
328    assert value_str(1235670000,76400) == "1.235670e9 +/- 0.000076e9"
329
330    # val_place == err_place
331    assert value_str(123567,764000) == "0.12e6 +/- 0.76e6"
332    assert value_str(12356.7,76400) == "12e3 +/- 76e3"
333    assert value_str(1235.67,7640) == "1.2e3 +/- 7.6e3"
334    assert value_str(123.567,764) == "0.12e3 +/- 0.76e3"
335    assert value_str(12.3567,76.4) == "12 +/- 76"
336    assert value_str(1.23567,7.64) == "1.2 +/- 7.6"
337    assert value_str(.123567,.764) == "0.12 +/- 0.76"
338    assert value_str(.0123567,.0764) == "12e-3 +/- 76e-3"
339    assert value_str(.00123567,.00764) == "1.2e-3 +/- 7.6e-3"
340    assert value_str(.000123567,.000764) == "0.12e-3 +/- 0.76e-3"
341
342    # val_place == err_place-1
343    assert value_str(123567,7640000) == "0.1e6 +/- 7.6e6"
344    assert value_str(12356.7,764000) == "0.01e6 +/- 0.76e6"
345    assert value_str(1235.67,76400) == "0.001e6 +/- 0.076e6"
346    assert value_str(123.567,7640) == "0.1e3 +/- 7.6e3"
347    assert value_str(12.3567,764) == "0.01e3 +/- 0.76e3"
348    assert value_str(1.23567,76.4) == "0.001e3 +/- 0.076e3"
349    assert value_str(.123567,7.64) == "0.1 +/- 7.6"
350    assert value_str(.0123567,.764) == "0.01 +/- 0.76"
351    assert value_str(.00123567,.0764) == "0.001 +/- 0.076"
352    assert value_str(.000123567,.00764) == "0.1e-3 +/- 7.6e-3"
353
354    # val_place == err_place-2
355    assert value_str(12356700,7640000000) == "0.0e9 +/- 7.6e9"
356    assert value_str(1235670,764000000) == "0.00e9 +/- 0.76e9"
357    assert value_str(123567,76400000) == "0.000e9 +/- 0.076e9"
358    assert value_str(12356,7640000) == "0.0e6 +/- 7.6e6"
359    assert value_str(1235,764000) == "0.00e6 +/- 0.76e6"
360    assert value_str(123,76400) == "0.000e6 +/- 0.076e6"
361    assert value_str(12,7640) == "0.0e3 +/- 7.6e3"
362    assert value_str(1,764) == "0.00e3 +/- 0.76e3"
363    assert value_str(0.1,76.4) == "0.000e3 +/- 0.076e3"
364    assert value_str(0.01,7.64) == "0.0 +/- 7.6"
365    assert value_str(0.001,0.764) == "0.00 +/- 0.76"
366    assert value_str(0.0001,0.0764) == "0.000 +/- 0.076"
367    assert value_str(0.00001,0.00764) == "0.0e-3 +/- 7.6e-3"
368
369    # val_place == err_place-3
370    assert value_str(12356700,76400000000) == "0.000e12 +/- 0.076e12"
371    assert value_str(1235670,7640000000) == "0.0e9 +/- 7.6e9"
372    assert value_str(123567,764000000) == "0.00e9 +/- 0.76e9"
373    assert value_str(12356,76400000) == "0.000e9 +/- 0.076e9"
374    assert value_str(1235,7640000) == "0.0e6 +/- 7.6e6"
375    assert value_str(123,764000) == "0.00e6 +/- 0.76e6"
376    assert value_str(12,76400) == "0.000e6 +/- 0.076e6"
377    assert value_str(1,7640) == "0.0e3 +/- 7.6e3"
378    assert value_str(0.1,764) == "0.00e3 +/- 0.76e3"
379    assert value_str(0.01,76.4) == "0.000e3 +/- 0.076e3"
380    assert value_str(0.001,7.64) == "0.0 +/- 7.6"
381    assert value_str(0.0001,0.764) == "0.00 +/- 0.76"
382    assert value_str(0.00001,0.0764) == "0.000 +/- 0.076"
383    assert value_str(0.000001,0.00764) == "0.0e-3 +/- 7.6e-3"
384
385    # Zero values
386    assert value_str(0,7640000) == "0.0e6 +/- 7.6e6"
387    assert value_str(0, 764000) == "0.00e6 +/- 0.76e6"
388    assert value_str(0,  76400) == "0.000e6 +/- 0.076e6"
389    assert value_str(0,   7640) == "0.0e3 +/- 7.6e3"
390    assert value_str(0,    764) == "0.00e3 +/- 0.76e3"
391    assert value_str(0,     76.4) == "0.000e3 +/- 0.076e3"
392    assert value_str(0,      7.64) == "0.0 +/- 7.6"
393    assert value_str(0,      0.764) == "0.00 +/- 0.76"
394    assert value_str(0,      0.0764) == "0.000 +/- 0.076"
395    assert value_str(0,      0.00764) == "0.0e-3 +/- 7.6e-3"
396    assert value_str(0,      0.000764) == "0.00e-3 +/- 0.76e-3"
397    assert value_str(0,      0.0000764) == "0.000e-3 +/- 0.076e-3"
398
399    # negative values
400    assert value_str(-1235670,766000) == "-1.24e6 +/- 0.77e6"
401    assert value_str(-1.23567,.766) == "-1.24 +/- 0.77"
402    assert value_str(-.00000123567,.0000000766) == "-1.236e-6 +/- 0.077e-6"
403    assert value_str(-12356.7,7.64) == "-12356.7 +/- 7.6"
404    assert value_str(-123.567,764) == "-0.12e3 +/- 0.76e3"
405    assert value_str(-1235.67,76400) == "-0.001e6 +/- 0.076e6"
406    assert value_str(-.000123567,.00764) == "-0.1e-3 +/- 7.6e-3"
407    assert value_str(-12356,7640000) == "-0.0e6 +/- 7.6e6"
408    assert value_str(-12,76400) == "-0.000e6 +/- 0.076e6"
409    assert value_str(-0.0001,0.764) == "-0.00 +/- 0.76"
410
411    # non-finite values
412    assert value_str(-np.inf,None) == "-inf"
413    assert value_str(np.inf,None) == "inf"
414    assert value_str(np.NaN,None) == "NaN"
415   
416    # bad or missing uncertainty
417    assert value_str(-1.23567,np.NaN) == "-1.23567"
418    assert value_str(-1.23567,-np.inf) == "-1.23567"
419    assert value_str(-1.23567,-0.1) == "-1.23567"
420    assert value_str(-1.23567,0) == "-1.23567"
421    assert value_str(-1.23567,None) == "-1.23567"
422    assert value_str(-1.23567,np.inf) == "-1.2 +/- inf"
423
424def test_default():
425    # Check that the default is the compact format
426    assert format_uncertainty(-1.23567,0.766) == "-1.24(77)"
427
428def main():
429    """
430    Run all tests.
431
432    This is equivalent to "nosetests --with-doctest"
433    """
434    test_compact()
435    test_pm()
436    test_default()
437   
438    import doctest
439    doctest.testmod()
440
441if __name__ == "__main__": main()
Note: See TracBrowser for help on using the repository browser.