1f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#!/usr/local/bin/perl
2f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#  ********************************************************************
3f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#  * COPYRIGHT:
4f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#  * Copyright (c) 2002, International Business Machines Corporation and
5f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#  * others. All Rights Reserved.
6f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#  ********************************************************************
7f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
8f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)my $PLUS_MINUS = "±";
9f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
10f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#---------------------------------------------------------------------
11f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|# Format a confidence interval, as given by a Dataset.  Output is as
12f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|# as follows:
13f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   241.23 - 241.98 => 241.5 +/- 0.3
14f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   241.2 - 243.8 => 242 +/- 1
15f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   211.0 - 241.0 => 226 +/- 15 or? 230 +/- 20
16f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   220.3 - 234.3 => 227 +/- 7
17f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   220.3 - 300.3 => 260 +/- 40
18f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   220.3 - 1000 => 610 +/- 390 or? 600 +/- 400
19f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   0.022 - 0.024 => 0.023 +/- 0.001
20f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   0.022 - 0.032 => 0.027 +/- 0.005
21f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|#   0.022 - 1.000 => 0.5 +/- 0.5
22f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|# In other words, take one significant digit of the error value and
23f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|# display the mean to the same precision.
24f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|sub formatDataset {
25f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    my $ds = shift;
26f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    my $lower = $ds->getMean() - $ds->getError();
27f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    my $upper = $ds->getMean() + $ds->getError();
28f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    my $scale = 0;
29f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    # Find how many initial digits are the same
30f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    while ($lower < 1 ||
31f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|           int($lower) == int($upper)) {
32f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|        $lower *= 10;
33f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|        $upper *= 10;
34f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|        $scale++;
35f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    }
36f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    while ($lower >= 10 &&
37f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|           int($lower) == int($upper)) {
38f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|        $lower /= 10;
39f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|        $upper /= 10;
40f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|        $scale--;
41f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|    }
42f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#|}
43f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
44f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#---------------------------------------------------------------------
45f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# Format a number, optionally with a +/- delta, to n significant
46f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# digits.
47f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
48f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param significant digit, a value >= 1
49f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param multiplier
50f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param time in seconds to be formatted
51f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @optional delta in seconds
52f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
53f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @return string of the form "23" or "23 +/- 10".
54f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
55f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)sub formatNumber {
56f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $sigdig = shift;
57f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $mult = shift;
58f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $a = shift;
59f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $delta = shift; # may be undef
60f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
61f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $result = formatSigDig($sigdig, $a*$mult);
62f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    if (defined($delta)) {
63f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        my $d = formatSigDig($sigdig, $delta*$mult);
64f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        # restrict PRECISION of delta to that of main number
65f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        if ($result =~ /\.(\d+)/) {
66f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            # TODO make this work for values with all significant
67f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            # digits to the left of the decimal, e.g., 1234000.
68f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
69f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            # TODO the other thing wrong with this is that it
70f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            # isn't rounding the $delta properly.  Have to put
71f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            # this logic into formatSigDig().
72f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            my $x = length($1);
73f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            $d =~ s/\.(\d{$x})\d+/.$1/;
74f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        }
75f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        $result .= " $PLUS_MINUS " . $d;
76f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    }
77f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    $result;
78f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)}
79f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
80f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#---------------------------------------------------------------------
81f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# Format a time, optionally with a +/- delta, to n significant
82f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# digits.
83f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
84f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param significant digit, a value >= 1
85f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param time in seconds to be formatted
86f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @optional delta in seconds
87f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
88f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @return string of the form "23 ms" or "23 +/- 10 ms".
89f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
90f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)sub formatSeconds {
91f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $sigdig = shift;
92f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $a = shift;
93f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $delta = shift; # may be undef
94f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
95f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my @MULT = (1   , 1e3,  1e6,  1e9);
96f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my @SUFF = ('s' , 'ms', 'us', 'ns');
97f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
98f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    # Determine our scale
99f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $i = 0;
100f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    #always do seconds if the following line is commented out
101f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    ++$i while ($a*$MULT[$i] < 1 && $i < @MULT);
102f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
103f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    formatNumber($sigdig, $MULT[$i], $a, $delta) . ' ' . $SUFF[$i];
104f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)}
105f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
106f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#---------------------------------------------------------------------
107f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# Format a percentage, optionally with a +/- delta, to n significant
108f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# digits.
109f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
110f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param significant digit, a value >= 1
111f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param value to be formatted, as a fraction, e.g. 0.5 for 50%
112f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @optional delta, as a fraction
113f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
114f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @return string of the form "23 %" or "23 +/- 10 %".
115f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
116f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)sub formatPercent {
117f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $sigdig = shift;
118f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $a = shift;
119f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $delta = shift; # may be undef
120f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
121f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    formatNumber($sigdig, 100, $a, $delta) . '%';
122f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)}
123f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
124f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#---------------------------------------------------------------------
125f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# Format a number to n significant digits without using exponential
126f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# notation.
127f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
128f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param significant digit, a value >= 1
129f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @param number to be formatted
130f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
131f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)# @return string of the form "1234" "12.34" or "0.001234".  If
132f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#         number was negative, prefixed by '-'.
133f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#
134f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)sub formatSigDig {
135f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $n = shift() - 1;
136f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $a = shift;
137f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
138f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    local $_ = sprintf("%.${n}e", $a);
139f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $sign = (s/^-//) ? '-' : '';
140f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
141f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $a_e;
142f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    my $result;
143f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    if (/^(\d)\.(\d+)e([-+]\d+)$/) {
144f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        my ($d, $dn, $e) = ($1, $2, $3);
145f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        $a_e = $e;
146f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        $d .= $dn;
147f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        $e++;
148f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        $d .= '0' while ($e > length($d));
149f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        while ($e < 1) {
150f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            $e++;
151f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            $d = '0' . $d;
152f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        }
153f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        if ($e == length($d)) {
154f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            $result = $sign . $d;
155f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        } else {
156f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)            $result = $sign . substr($d, 0, $e) . '.' . substr($d, $e);
157f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        }
158f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    } else {
159f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)        die "Can't parse $_";
160f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    }
161f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)    $result;
162f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)}
163f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
164f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)1;
165f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)
166f4ed1cf5d184064c4cf0e4359c6d5d8aadb50afaTorne (Richard Coles)#eof
167