Project/build/packaging adjustments + version 1.3
[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;
3a3b5f3c 26 boolean forceExact = false;
3f5430db
MM
27 if (space == -1) {
28 numPart = str;
29 unitPart = "";
3a3b5f3c 30 forceExact = true;
3f5430db
MM
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) {
3a3b5f3c 38 forceExact = true;
3f5430db 39 numPart = numPart.substring(0, numPart.length() - 1);
3a3b5f3c
MM
40 }
41 num = Double.parseDouble(numPart);
42 if (forceExact)
3f5430db 43 unc = 0;
3a3b5f3c 44 else {
3f5430db
MM
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) {
3a3b5f3c 120 return (d == 0) ? 0 : Math.floor(Math.log10(Math.abs(d)));
3f5430db
MM
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}