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