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; | |
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 | } |