Initial commit of Matt's measurement add-in for OpenOffice.org Calc.
[measurements/measurements.git] / src / net / mattmccutchen / measurements / Measurement.java
CommitLineData
3f5430db
MM
1package net.mattmccutchen.measurements;
2
3import java.text.*;
4
5public 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}