Project/build/packaging adjustments + version 1.3
[measurements/measurements.git] / src / net / mattmccutchen / measurements / Measurement.java
1 package net.mattmccutchen.measurements;
2
3 import java.text.*;
4
5 public class Measurement {
6         public static final int dataLen = 2 * 8 + Unit.basicUnits.length * 1;
7         public static final String errorIndicator = "ERROR";
8         public static final char exactIndicator = 'x';
9         public static final int[] pureNumberUnitPowers = new int[Unit.basicUnits.length];
10         
11         public final double number;
12         public final double uncertainty;
13         public final int[] unitPowers; // Don't mutate
14         
15         public Measurement(double number, double uncertainty, int... unitPowers) {
16                 this.number = number;
17                 this.uncertainty = uncertainty;
18                 this.unitPowers = unitPowers;
19         }
20         
21         public static Measurement parse(String str) {
22                 try {
23                         int space = str.indexOf(' ');
24                         String numPart;
25                         String unitPart;
26                         boolean forceExact = false;
27                         if (space == -1) {
28                                 numPart = str;
29                                 unitPart = "";
30                                 forceExact = true;
31                         } else {
32                                 numPart = str.substring(0, space);
33                                 unitPart = str.substring(space + 1);
34                         }
35                         double num;
36                         double unc;
37                         if (numPart.charAt(numPart.length() - 1) == exactIndicator) {
38                                 forceExact = true;
39                                 numPart = numPart.substring(0, numPart.length() - 1);
40                         }
41                         num = Double.parseDouble(numPart);
42                         if (forceExact)
43                                 unc = 0;
44                         else {
45                                 // Determine sig figs
46                                 int echar = numPart.indexOf('e');
47                                 if (echar == -1) {
48                                         echar = numPart.indexOf('E');
49                                         if (echar == -1)
50                                                 echar = numPart.length();
51                                 }
52                                 int decpt = numPart.indexOf('.');
53                                 int sigFigs;
54                                 if (decpt == -1) {
55                                         // Everything but trailing zeros.  Yuck.
56                                         int i = echar;
57                                         while (i > 1 /* 0 gets one sig fig */
58                                                                         && numPart.charAt(i - 1) == '0')
59                                                 i--;
60                                         sigFigs = i;
61                                 } else
62                                         sigFigs = echar - 1; // Everything except the decimal point
63                                 unc = Math.pow(10.0, expOf(num) - sigFigs);
64                         }
65                         
66                         Unit theUnit = Unit.parseUnitString(unitPart);
67                         num *= theUnit.factor;
68                         unc *= theUnit.factor;
69                         return new Measurement(num, unc, theUnit.powers);
70                 } catch (Exception e) {
71                         return null;
72                 }
73         }
74         
75         public static Measurement parseCode(String str) {
76                 if (str.indexOf(errorIndicator) != -1)
77                         return null;
78                 // Does it have a code?
79                 int lbrack = str.indexOf('[');
80                 if (lbrack != -1) {
81                         int rbrack = str.indexOf(']', lbrack + 1);
82                         String code = str.substring(lbrack + 1, rbrack);
83                         byte[] data = Base64.decode(code, Base64.NO_GZIP_MAGIC);
84                         return fromData(data);
85                 } else
86                         return parse(str);
87         }
88         
89         public static Measurement fromData(byte[] data) {
90                 /*
91                 System.err.print("Loading data: ");
92                 for (int i = 0; i < data.length; i++)
93                         System.err.print(" " + data[i]);
94                 System.err.println();
95                 */
96                 double num = BitFlicking.loadDouble(data, 0);
97                 double unc = BitFlicking.loadDouble(data, 8);
98                 int[] up = new int[Unit.basicUnits.length];
99                 for (int i = 0; i < up.length; i++)
100                         up[i] = data[16+i];
101                 return new Measurement(num, unc, up);
102         }
103         
104         public byte[] toData() {
105                 byte[] data = new byte[dataLen];
106                 BitFlicking.storeDouble(data, number, 0);
107                 BitFlicking.storeDouble(data, uncertainty, 8);
108                 for (int i = 0; i < unitPowers.length; i++)
109                         data[16+i] = (byte) unitPowers[i];
110                 /*
111                 System.err.print("Storing data: ");
112                 for (int i = 0; i < data.length; i++)
113                         System.err.print(" " + data[i]);
114                 System.err.println();
115                 */
116                 return data;
117         }
118         
119         private static double expOf(double d) {
120                 return (d == 0) ? 0 : Math.floor(Math.log10(Math.abs(d)));
121         }
122         
123         private static int sigFigsOf(double num, double unc) {
124                 if (unc == 0)
125                         return Integer.MAX_VALUE;
126                 //System.err.println("number " + number + ", unc " + uncertainty);
127                 int sf = (int) Math.round(expOf(num) - Math.log10(unc));
128                 //System.err.println("sigFigs: " + sf);
129                 return sf;
130         }
131         
132         private static DecimalFormat formatForSigFigs(int sigFigs) {
133                 StringBuilder fmtString = new StringBuilder("0.");
134                 for (int i = 0; i < sigFigs-1; i++)
135                         fmtString.append('0');
136                 fmtString.append("E0");
137                 //System.err.println("fmtString is " + fmtString);
138                 return new DecimalFormat(fmtString.toString());
139         }
140         
141         private static final DecimalFormat exactFormat
142                 = new DecimalFormat("0.##########E0");
143         
144         private static void formatNum(double num, double unc, StringBuilder sb) {
145                 int sigFigs = sigFigsOf(num, unc);
146                 if (num == 0 && unc == 0)
147                         sb.append("0" + exactIndicator);
148                 else if (sigFigs > 10 && num != 0) {
149                         // Put some reasonable number of digits and mark exact
150                         sb.append(exactFormat.format(num));
151                         sb.append(exactIndicator);
152                 } else if (num == 0) {
153                         // Handle zero specially
154                         sb.append("0.E");
155                         sb.append(-(sigFigs - 1));
156                 } else if (sigFigs >= 1)
157                         sb.append(formatForSigFigs(sigFigs).format(num));
158                 else
159                         sb.append("insignificant");
160         }
161         
162         public static String format(Measurement m, boolean withCode) {
163                 if (m == null)
164                         return errorIndicator;
165                 StringBuilder sb = new StringBuilder();
166                 formatNum(m.number, m.uncertainty, sb);
167                 
168                 // Units
169                 for (int i = 0; i < m.unitPowers.length; i++)
170                         if (m.unitPowers[i] != 0) {
171                                 sb.append(' ');
172                                 sb.append(Unit.basicUnits[i].symbol);
173                                 if (m.unitPowers[i] != 1) {
174                                         sb.append('^');
175                                         sb.append(Integer.toString(m.unitPowers[i]));
176                                 }
177                         }
178                 
179                 if (withCode) {
180                         sb.append("    [");
181                         sb.append(Base64.encodeBytes(m.toData(), Base64.DONT_BREAK_LINES));
182                         sb.append(']');
183                 }
184                 return sb.toString();
185         }
186         
187         public static String formatInUnit(Measurement m, Unit u, boolean withCode) {
188                 if (m == null)
189                         return errorIndicator;
190                 for (int i = 0; i < Unit.basicUnits.length; i++)
191                         if (m.unitPowers[i] != u.powers[i])
192                                 return errorIndicator;
193                 StringBuilder sb = new StringBuilder();
194                 formatNum(m.number / u.factor, m.uncertainty / u.factor, sb);
195                 sb.append(' ');
196                 sb.append(u.symbol);
197                 
198                 if (withCode) {
199                         sb.append("    [");
200                         sb.append(Base64.encodeBytes(m.toData(), Base64.DONT_BREAK_LINES));
201                         sb.append(']');
202                 }
203                 return sb.toString();
204         }
205 }