[3570545] | 1 | # This program is public domain |
---|
| 2 | # Author Paul Kienzle |
---|
| 3 | """ |
---|
| 4 | Format numbers nicely for printing. |
---|
| 5 | |
---|
| 6 | Usage:: |
---|
| 7 | |
---|
| 8 | >> from danse.common.util.formatnum import * |
---|
| 9 | >> v,dv = 757.2356,0.01032 |
---|
| 10 | >> print format_uncertainty_pm(v,dv) |
---|
| 11 | 757.235 +/- 0.010 |
---|
| 12 | >> format_uncertainty_compact(v,dv) |
---|
| 13 | 757.235(10) |
---|
| 14 | >> format_uncertainty(v,dv) |
---|
| 15 | 757.235(10) |
---|
| 16 | |
---|
| 17 | Set format_uncertainty.compact to False to use the +/- |
---|
| 18 | format by default, otherwise leave it at True for compact |
---|
| 19 | value(##) format. |
---|
| 20 | """ |
---|
| 21 | from __future__ import division |
---|
| 22 | |
---|
| 23 | import math |
---|
| 24 | import numpy |
---|
| 25 | __all__ = ['format_uncertainty', 'format_uncertainty_pm', |
---|
| 26 | 'format_uncertainty_compact'] |
---|
| 27 | |
---|
| 28 | # These routines need work for +/- formatting:: |
---|
| 29 | # - pm formats are not rigorously tested |
---|
| 30 | # - pm formats do not try to align the scale to multiples of 1000 |
---|
| 31 | # - pm formats do not try to align the value scale to uncertainty scale |
---|
| 32 | |
---|
| 33 | # Coordinating scales across a set of numbers is not supported. For easy |
---|
| 34 | # comparison a set of numbers should be shown in the same scale. One could |
---|
| 35 | # force this from the outside by adding scale parameter (either 10**n, n, or |
---|
| 36 | # a string representing the desired SI prefix) and having a separate routine |
---|
| 37 | # which computes the scale given a set of values. |
---|
| 38 | |
---|
| 39 | # Coordinating scales with units offers its own problems. Again, the user |
---|
| 40 | # may want to force particular units. This can be done by outside of the |
---|
| 41 | # formatting routines by scaling the numbers to the appropriate units then |
---|
| 42 | # forcing them to print with scale 10**0. If this is a common operation, |
---|
| 43 | # however, it may want to happen inside. |
---|
| 44 | |
---|
| 45 | # The value e<n> is currently formatted into the number. Alternatively this |
---|
| 46 | # scale factor could be returned so that the user can choose the appriate |
---|
| 47 | # SI prefix when printing the units. This gets tricky when talking about |
---|
| 48 | # composite units such as 2.3e-3 m**2 -> 2300 mm**2, and with volumes |
---|
| 49 | # such as 1 g/cm**3 -> 1 kg/L. |
---|
| 50 | |
---|
| 51 | def format_uncertainty_pm(value, uncertainty=None): |
---|
| 52 | """ |
---|
| 53 | Given *value* v and *uncertainty* dv, return a string v +/- dv. |
---|
| 54 | |
---|
| 55 | The returned string uses only the number of digits warranted by |
---|
| 56 | the uncertainty in the measurement. |
---|
| 57 | |
---|
| 58 | If the uncertainty is 0 or not otherwise provided, the simple |
---|
| 59 | %g floating point format option is used. |
---|
| 60 | |
---|
| 61 | Infinite and indefinite numbers are represented as inf and NaN. |
---|
| 62 | """ |
---|
| 63 | return _format_uncertainty(value, uncertainty, compact=False) |
---|
| 64 | |
---|
| 65 | def format_uncertainty_compact(value, uncertainty=None): |
---|
| 66 | """ |
---|
| 67 | Given *value* v and *uncertainty* dv, return the compact |
---|
| 68 | representation v(##), where ## are the first two digits of |
---|
| 69 | the uncertainty. |
---|
| 70 | |
---|
| 71 | The returned string uses only the number of digits warranted by |
---|
| 72 | the uncertainty in the measurement. |
---|
| 73 | |
---|
| 74 | If the uncertainty is 0 or not otherwise provided, the simple |
---|
| 75 | %g floating point format option is used. |
---|
| 76 | |
---|
| 77 | Infinite and indefinite numbers are represented as inf and NaN. |
---|
| 78 | """ |
---|
| 79 | return _format_uncertainty(value, uncertainty, compact=True) |
---|
| 80 | |
---|
| 81 | class FormatUncertainty: |
---|
| 82 | """ |
---|
| 83 | Given *value* and *uncertainty*, return a concise string representation. |
---|
| 84 | |
---|
| 85 | This will either by the +/- form of :func:`format_uncertainty_pm` or |
---|
| 86 | the compact form of :func:`format_uncertainty_compact` depending on |
---|
| 87 | whether *compact* is specified or whether *format_uncertainty.compact* |
---|
| 88 | is True or False. |
---|
| 89 | """ |
---|
| 90 | compact = True |
---|
| 91 | def __call__(self, value, uncertainty): |
---|
| 92 | return _format_uncertainty(value, uncertainty, self.compact) |
---|
| 93 | format_uncertainty = FormatUncertainty() |
---|
| 94 | |
---|
| 95 | def _format_uncertainty(value,uncertainty,compact): |
---|
| 96 | """ |
---|
| 97 | Implementation of both the compact and the +/- formats. |
---|
| 98 | """ |
---|
| 99 | if numpy.isinf(value): |
---|
| 100 | return "inf" if value > 0 else "-inf" |
---|
| 101 | |
---|
| 102 | if numpy.isnan(value): |
---|
| 103 | return "NaN" |
---|
| 104 | |
---|
| 105 | # Uncertainty check must come after indefinite check since the %g |
---|
| 106 | # format string doesn't handle indefinite numbers consistently |
---|
| 107 | # across platforms. |
---|
| 108 | if uncertainty == None or uncertainty <= 0 or numpy.isnan(uncertainty): |
---|
| 109 | return "%g"%value |
---|
| 110 | if numpy.isinf(uncertainty): |
---|
| 111 | if compact: |
---|
| 112 | return "%g(inf)"%value |
---|
| 113 | else: |
---|
| 114 | return "%g +/- inf"%value |
---|
| 115 | |
---|
| 116 | # Process sign |
---|
| 117 | sign = "-" if value < 0 else "" |
---|
| 118 | value = abs(value) |
---|
| 119 | |
---|
| 120 | # Determine the number of digits in the value and the error |
---|
| 121 | # Note that uncertainty <= 0 is handled above |
---|
| 122 | err_place = int(math.floor(math.log10(uncertainty))) |
---|
| 123 | if value == 0: |
---|
| 124 | val_place = err_place-1 |
---|
| 125 | else: |
---|
| 126 | val_place = int(math.floor(math.log10(value))) |
---|
| 127 | |
---|
| 128 | # If pm, return a simple v +/- dv |
---|
| 129 | if not compact: |
---|
| 130 | scale = 10**(err_place-1) |
---|
| 131 | val_digits = val_place-err_place+2 |
---|
| 132 | return "%s%.*g +/- %.2g"%(sign,val_digits,value,uncertainty) |
---|
| 133 | |
---|
| 134 | # The remainder is for the v(dv) case |
---|
| 135 | err_str = "(%2d)"%int(uncertainty/10.**(err_place-1)+0.5) |
---|
| 136 | if err_place > val_place: |
---|
| 137 | # Degenerate case: error bigger than value |
---|
| 138 | # The mantissa is 0.#(##)e#, 0.0#(##)e# or 0.00#(##)e# |
---|
| 139 | if err_place - val_place > 2: value = 0 |
---|
| 140 | val_place = int(math.floor((err_place+2)/3.))*3 |
---|
| 141 | digits_after_decimal = val_place - err_place + 1 |
---|
| 142 | val_str = "%.*f%s"%(digits_after_decimal,value/10.**val_place,err_str) |
---|
| 143 | if val_place != 0: val_str += "e%d"%val_place |
---|
| 144 | elif err_place == val_place: |
---|
| 145 | # Degenerate case: error and value the same order of magnitude |
---|
| 146 | # The value is ##(##)e#, #.#(##)e# or 0.##(##)e# |
---|
| 147 | val_place = int(math.floor((err_place+1)/3.))*3 |
---|
| 148 | digits_after_decimal = val_place - err_place + 1 |
---|
| 149 | val_str = "%.*f%s"%(digits_after_decimal,value/10.**val_place,err_str) |
---|
| 150 | if val_place != 0: val_str += "e%d"%val_place |
---|
| 151 | elif err_place <= 1 and val_place >= -3: |
---|
| 152 | # Normal case: nice numbers and errors |
---|
| 153 | # The value is ###.###(##) |
---|
| 154 | digits_after_decimal = abs(err_place-1) |
---|
| 155 | val_str = "%.*f%s"%(digits_after_decimal,value,err_str) |
---|
| 156 | else: |
---|
| 157 | # Extreme cases: zeros before value or after error |
---|
| 158 | # The value is ###.###(##)e#, ##.####(##)e# or #.#####(##)e# |
---|
| 159 | total_digits = val_place - err_place + 2 |
---|
| 160 | val_place = int(math.floor(val_place/3.))*3 |
---|
| 161 | val_str = "%.*g%se%d"%(total_digits, |
---|
| 162 | value/10.**val_place, |
---|
| 163 | err_str,val_place) |
---|
| 164 | |
---|
| 165 | return sign+val_str |
---|
| 166 | |
---|
| 167 | |
---|
| 168 | |
---|
| 169 | def test(): |
---|
| 170 | # Oops... renamed function after writing tests |
---|
| 171 | value_str = format_uncertainty_compact |
---|
| 172 | |
---|
| 173 | # val_place > err_place |
---|
| 174 | assert value_str(1235670,766000) == "1.24(77)e6" |
---|
| 175 | assert value_str(123567.,76600) == "124(77)e3" |
---|
| 176 | assert value_str(12356.7,7660) == "12.4(77)e3" |
---|
| 177 | assert value_str(1235.67,766) == "1.24(77)e3" |
---|
| 178 | assert value_str(123.567,76.6) == "124(77)" |
---|
| 179 | assert value_str(12.3567,7.66) == "12.4(77)" |
---|
| 180 | assert value_str(1.23567,.766) == "1.24(77)" |
---|
| 181 | assert value_str(.123567,.0766) == "0.124(77)" |
---|
| 182 | assert value_str(.0123567,.00766) == "0.0124(77)" |
---|
| 183 | assert value_str(.00123567,.000766) == "0.00124(77)" |
---|
| 184 | assert value_str(.000123567,.0000766) == "124(77)e-6" |
---|
| 185 | assert value_str(.0000123567,.00000766) == "12.4(77)e-6" |
---|
| 186 | assert value_str(.00000123567,.000000766) == "1.24(77)e-6" |
---|
| 187 | assert value_str(.000000123567,.0000000766) == "124(77)e-9" |
---|
| 188 | assert value_str(.00000123567,.0000000766) == "1.236(77)e-6" |
---|
| 189 | assert value_str(.0000123567,.0000000766) == "12.357(77)e-6" |
---|
| 190 | assert value_str(.000123567,.0000000766) == "123.567(77)e-6" |
---|
| 191 | assert value_str(.00123567,.000000766) == "0.00123567(77)" |
---|
| 192 | assert value_str(.0123567,.00000766) == "0.0123567(77)" |
---|
| 193 | assert value_str(.123567,.0000766) == "0.123567(77)" |
---|
| 194 | assert value_str(1.23567,.000766) == "1.23567(77)" |
---|
| 195 | assert value_str(12.3567,.00766) == "12.3567(77)" |
---|
| 196 | assert value_str(123.567,.0764) == "123.567(76)" |
---|
| 197 | assert value_str(1235.67,.764) == "1235.67(76)" |
---|
| 198 | assert value_str(12356.7,7.64) == "12356.7(76)" |
---|
| 199 | assert value_str(123567,76.4) == "123567(76)" |
---|
| 200 | assert value_str(1235670,764) == "1.23567(76)e6" |
---|
| 201 | assert value_str(12356700,764) == "12.3567(76)e6" |
---|
| 202 | assert value_str(123567000,7640) == "123.567(76)e6" |
---|
| 203 | assert value_str(1235670000,76400) == "1.23567(76)e9" |
---|
| 204 | |
---|
| 205 | # val_place == err_place |
---|
| 206 | assert value_str(123567,764000) == "0.12(76)e6" |
---|
| 207 | assert value_str(12356.7,76400) == "12(76)e3" |
---|
| 208 | assert value_str(1235.67,7640) == "1.2(76)e3" |
---|
| 209 | assert value_str(123.567,764) == "0.12(76)e3" |
---|
| 210 | assert value_str(12.3567,76.4) == "12(76)" |
---|
| 211 | assert value_str(1.23567,7.64) == "1.2(76)" |
---|
| 212 | assert value_str(.123567,.764) == "0.12(76)" |
---|
| 213 | assert value_str(.0123567,.0764) == "12(76)e-3" |
---|
| 214 | assert value_str(.00123567,.00764) == "1.2(76)e-3" |
---|
| 215 | assert value_str(.000123567,.000764) == "0.12(76)e-3" |
---|
| 216 | |
---|
| 217 | # val_place == err_place-1 |
---|
| 218 | assert value_str(123567,7640000) == "0.1(76)e6" |
---|
| 219 | assert value_str(12356.7,764000) == "0.01(76)e6" |
---|
| 220 | assert value_str(1235.67,76400) == "0.001(76)e6" |
---|
| 221 | assert value_str(123.567,7640) == "0.1(76)e3" |
---|
| 222 | assert value_str(12.3567,764) == "0.01(76)e3" |
---|
| 223 | assert value_str(1.23567,76.4) == "0.001(76)e3" |
---|
| 224 | assert value_str(.123567,7.64) == "0.1(76)" |
---|
| 225 | assert value_str(.0123567,.764) == "0.01(76)" |
---|
| 226 | assert value_str(.00123567,.0764) == "0.001(76)" |
---|
| 227 | assert value_str(.000123567,.00764) == "0.1(76)e-3" |
---|
| 228 | |
---|
| 229 | # val_place == err_place-2 |
---|
| 230 | assert value_str(12356700,7640000000) == "0.0(76)e9" |
---|
| 231 | assert value_str(1235670,764000000) == "0.00(76)e9" |
---|
| 232 | assert value_str(123567,76400000) == "0.000(76)e9" |
---|
| 233 | assert value_str(12356,7640000) == "0.0(76)e6" |
---|
| 234 | assert value_str(1235,764000) == "0.00(76)e6" |
---|
| 235 | assert value_str(123,76400) == "0.000(76)e6" |
---|
| 236 | assert value_str(12,7640) == "0.0(76)e3" |
---|
| 237 | assert value_str(1,764) == "0.00(76)e3" |
---|
| 238 | assert value_str(0.1,76.4) == "0.000(76)e3" |
---|
| 239 | assert value_str(0.01,7.64) == "0.0(76)" |
---|
| 240 | assert value_str(0.001,0.764) == "0.00(76)" |
---|
| 241 | assert value_str(0.0001,0.0764) == "0.000(76)" |
---|
| 242 | assert value_str(0.00001,0.00764) == "0.0(76)e-3" |
---|
| 243 | |
---|
| 244 | # val_place == err_place-3 |
---|
| 245 | assert value_str(12356700,76400000000) == "0.000(76)e12" |
---|
| 246 | assert value_str(1235670,7640000000) == "0.0(76)e9" |
---|
| 247 | assert value_str(123567,764000000) == "0.00(76)e9" |
---|
| 248 | assert value_str(12356,76400000) == "0.000(76)e9" |
---|
| 249 | assert value_str(1235,7640000) == "0.0(76)e6" |
---|
| 250 | assert value_str(123,764000) == "0.00(76)e6" |
---|
| 251 | assert value_str(12,76400) == "0.000(76)e6" |
---|
| 252 | assert value_str(1,7640) == "0.0(76)e3" |
---|
| 253 | assert value_str(0.1,764) == "0.00(76)e3" |
---|
| 254 | assert value_str(0.01,76.4) == "0.000(76)e3" |
---|
| 255 | assert value_str(0.001,7.64) == "0.0(76)" |
---|
| 256 | assert value_str(0.0001,0.764) == "0.00(76)" |
---|
| 257 | assert value_str(0.00001,0.0764) == "0.000(76)" |
---|
| 258 | assert value_str(0.000001,0.00764) == "0.0(76)e-3" |
---|
| 259 | |
---|
| 260 | # negative values |
---|
| 261 | assert value_str(-1235670,765000) == "-1.24(77)e6" |
---|
| 262 | assert value_str(-1.23567,.765) == "-1.24(77)" |
---|
| 263 | assert value_str(-.00000123567,.0000000765) == "-1.236(77)e-6" |
---|
| 264 | assert value_str(-12356.7,7.64) == "-12356.7(76)" |
---|
| 265 | assert value_str(-123.567,764) == "-0.12(76)e3" |
---|
| 266 | assert value_str(-1235.67,76400) == "-0.001(76)e6" |
---|
| 267 | assert value_str(-.000123567,.00764) == "-0.1(76)e-3" |
---|
| 268 | assert value_str(-12356,7640000) == "-0.0(76)e6" |
---|
| 269 | assert value_str(-12,76400) == "-0.000(76)e6" |
---|
| 270 | assert value_str(-0.0001,0.764) == "-0.00(76)" |
---|
| 271 | |
---|
| 272 | # zero values |
---|
| 273 | assert value_str(0,0) == "0" |
---|
| 274 | assert value_str(-1.23567,0) == "-1.23567" |
---|
| 275 | assert value_str(0,765000) == "0.00(77)e6" |
---|
| 276 | assert value_str(0,.765) == "0.00(77)" |
---|
| 277 | assert value_str(0,.000000765) == "0.00(77)e-6" |
---|
| 278 | assert value_str(0,76400) == "0.000(76)e6" |
---|
| 279 | assert value_str(0,7640) == "0.0(76)e3" |
---|
| 280 | |
---|
| 281 | # marked values |
---|
| 282 | assert value_str(-numpy.inf,None) == "-inf" |
---|
| 283 | assert value_str(numpy.inf,None) == "inf" |
---|
| 284 | assert value_str(numpy.NaN,None) == "NaN" |
---|
| 285 | |
---|
| 286 | # plus/minus form |
---|
| 287 | assert format_uncertainty_pm(-1.23567,0.765) == "-1.24 +/- 0.77" |
---|
| 288 | assert format_uncertainty_compact(-1.23567,0.765) == "-1.24(77)" |
---|
| 289 | assert format_uncertainty_pm(752.3567,0.01) == "752.357 +/- 0.01" |
---|
| 290 | assert format_uncertainty(-1.23567,0.765) == "-1.24(77)" |
---|
| 291 | |
---|
| 292 | # bad uncertainty |
---|
| 293 | assert format_uncertainty_pm(-1.23567,numpy.NaN) == "-1.23567" |
---|
| 294 | assert format_uncertainty_pm(-1.23567,-numpy.inf) == "-1.23567" |
---|
| 295 | assert format_uncertainty_pm(-1.23567,-0.1) == "-1.23567" |
---|
| 296 | assert format_uncertainty_compact(-1.23567,numpy.NaN) == "-1.23567" |
---|
| 297 | assert format_uncertainty_compact(-1.23567,-numpy.inf) == "-1.23567" |
---|
| 298 | assert format_uncertainty_compact(-1.23567,-0.1) == "-1.23567" |
---|
| 299 | |
---|
| 300 | # no uncertainty |
---|
| 301 | assert format_uncertainty_pm(-1.23567,0) == "-1.23567" |
---|
| 302 | assert format_uncertainty_compact(-1.23567,0) == "-1.23567" |
---|
| 303 | assert format_uncertainty_pm(-1.23567,None) == "-1.23567" |
---|
| 304 | assert format_uncertainty_compact(-1.23567,None) == "-1.23567" |
---|
| 305 | |
---|
| 306 | # inf uncertainty |
---|
| 307 | assert format_uncertainty_pm(-1.23567,numpy.inf) == "-1.23567 +/- inf" |
---|
| 308 | assert format_uncertainty_compact(-1.23567,numpy.inf) == "-1.23567(inf)" |
---|
| 309 | |
---|
| 310 | |
---|
| 311 | if __name__ == "__main__": test() |
---|
| 312 | |
---|