Commit | Line | Data |
---|---|---|
3f5430db MM |
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 | } |