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