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 12 my $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 # 59 sub 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 # 94 sub 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 # 120 sub 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 # 138 sub 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 168 1; 169 170 #eof 171