package net.mattmccutchen.measurements; import java.text.*; public class Measurement { public static final int dataLen = 2 * 8 + Unit.basicUnits.length * 1; public static final String errorIndicator = "ERROR"; public static final char exactIndicator = 'x'; public static final int[] pureNumberUnitPowers = new int[Unit.basicUnits.length]; public final double number; public final double uncertainty; public final int[] unitPowers; // Don't mutate public Measurement(double number, double uncertainty, int... unitPowers) { this.number = number; this.uncertainty = uncertainty; this.unitPowers = unitPowers; } public static Measurement parse(String str) { try { int space = str.indexOf(' '); String numPart; String unitPart; boolean forceExact = false; if (space == -1) { numPart = str; unitPart = ""; forceExact = true; } else { numPart = str.substring(0, space); unitPart = str.substring(space + 1); } double num; double unc; if (numPart.charAt(numPart.length() - 1) == exactIndicator) { forceExact = true; numPart = numPart.substring(0, numPart.length() - 1); } num = Double.parseDouble(numPart); if (forceExact) unc = 0; else { // Determine sig figs int echar = numPart.indexOf('e'); if (echar == -1) { echar = numPart.indexOf('E'); if (echar == -1) echar = numPart.length(); } int decpt = numPart.indexOf('.'); int sigFigs; if (decpt == -1) { // Everything but trailing zeros. Yuck. int i = echar; while (i > 1 /* 0 gets one sig fig */ && numPart.charAt(i - 1) == '0') i--; sigFigs = i; } else sigFigs = echar - 1; // Everything except the decimal point unc = Math.pow(10.0, expOf(num) - sigFigs); } Unit theUnit = Unit.parseUnitString(unitPart); num *= theUnit.factor; unc *= theUnit.factor; return new Measurement(num, unc, theUnit.powers); } catch (Exception e) { return null; } } public static Measurement parseCode(String str) { if (str.indexOf(errorIndicator) != -1) return null; // Does it have a code? int lbrack = str.indexOf('['); if (lbrack != -1) { int rbrack = str.indexOf(']', lbrack + 1); String code = str.substring(lbrack + 1, rbrack); byte[] data = Base64.decode(code, Base64.NO_GZIP_MAGIC); return fromData(data); } else return parse(str); } public static Measurement fromData(byte[] data) { /* System.err.print("Loading data: "); for (int i = 0; i < data.length; i++) System.err.print(" " + data[i]); System.err.println(); */ double num = BitFlicking.loadDouble(data, 0); double unc = BitFlicking.loadDouble(data, 8); int[] up = new int[Unit.basicUnits.length]; for (int i = 0; i < up.length; i++) up[i] = data[16+i]; return new Measurement(num, unc, up); } public byte[] toData() { byte[] data = new byte[dataLen]; BitFlicking.storeDouble(data, number, 0); BitFlicking.storeDouble(data, uncertainty, 8); for (int i = 0; i < unitPowers.length; i++) data[16+i] = (byte) unitPowers[i]; /* System.err.print("Storing data: "); for (int i = 0; i < data.length; i++) System.err.print(" " + data[i]); System.err.println(); */ return data; } private static double expOf(double d) { return (d == 0) ? 0 : Math.floor(Math.log10(Math.abs(d))); } private static int sigFigsOf(double num, double unc) { if (unc == 0) return Integer.MAX_VALUE; //System.err.println("number " + number + ", unc " + uncertainty); int sf = (int) Math.round(expOf(num) - Math.log10(unc)); //System.err.println("sigFigs: " + sf); return sf; } private static DecimalFormat formatForSigFigs(int sigFigs) { StringBuilder fmtString = new StringBuilder("0."); for (int i = 0; i < sigFigs-1; i++) fmtString.append('0'); fmtString.append("E0"); //System.err.println("fmtString is " + fmtString); return new DecimalFormat(fmtString.toString()); } private static final DecimalFormat exactFormat = new DecimalFormat("0.##########E0"); private static void formatNum(double num, double unc, StringBuilder sb) { int sigFigs = sigFigsOf(num, unc); if (num == 0 && unc == 0) sb.append("0" + exactIndicator); else if (sigFigs > 10 && num != 0) { // Put some reasonable number of digits and mark exact sb.append(exactFormat.format(num)); sb.append(exactIndicator); } else if (num == 0) { // Handle zero specially sb.append("0.E"); sb.append(-(sigFigs - 1)); } else if (sigFigs >= 1) sb.append(formatForSigFigs(sigFigs).format(num)); else sb.append("insignificant"); } public static String format(Measurement m, boolean withCode) { if (m == null) return errorIndicator; StringBuilder sb = new StringBuilder(); formatNum(m.number, m.uncertainty, sb); // Units for (int i = 0; i < m.unitPowers.length; i++) if (m.unitPowers[i] != 0) { sb.append(' '); sb.append(Unit.basicUnits[i].symbol); if (m.unitPowers[i] != 1) { sb.append('^'); sb.append(Integer.toString(m.unitPowers[i])); } } if (withCode) { sb.append(" ["); sb.append(Base64.encodeBytes(m.toData(), Base64.DONT_BREAK_LINES)); sb.append(']'); } return sb.toString(); } public static String formatInUnit(Measurement m, Unit u, boolean withCode) { if (m == null) return errorIndicator; for (int i = 0; i < Unit.basicUnits.length; i++) if (m.unitPowers[i] != u.powers[i]) return errorIndicator; StringBuilder sb = new StringBuilder(); formatNum(m.number / u.factor, m.uncertainty / u.factor, sb); sb.append(' '); sb.append(u.symbol); if (withCode) { sb.append(" ["); sb.append(Base64.encodeBytes(m.toData(), Base64.DONT_BREAK_LINES)); sb.append(']'); } return sb.toString(); } }