package ie.dcu.matrix; /** * Handles formating matrices so that all columns are equal width. * * @author Kevin McGuinness */ public class MatrixFormatter { private static final String NaN = "NaN"; private static final String Inf = "Inf"; // Options private boolean supressSmallValues; private boolean signAlwaysShown; private int precision; // Internal variables private boolean expFormat; private boolean largeExponent; private String numberFormat; private String specialFormat; private int maxStrLen; public MatrixFormatter() { // Setup defaults supressSmallValues = false; signAlwaysShown = false; precision = 8; } public boolean isSupressSmallValues() { return supressSmallValues; } public void setSupressSmallValues(boolean value) { this.supressSmallValues = value; } public boolean isSignAlwaysShown() { return signAlwaysShown; } public void setSignAlwaysShown(boolean value) { this.signAlwaysShown = value; } public int getPrecision() { return precision; } public void setPrecision(int precision) { if (precision < 0) { throw new IllegalArgumentException("precision < 0"); } this.precision = precision; } public String format(Matrix m) { return format(m, null).toString(); } public StringBuffer format(Matrix m, StringBuffer s) { if (s == null) { s = new StringBuffer(); } if (!m.isEmpty()) { switch (m.type) { case Byte: case Short: case Int: case Long: formatIntegerMatrix(m, s); break; default: formatDecimalMatrix(m, s); break; } } return s; } public static String toString(Matrix m) { return new MatrixFormatter().format(m, null).toString(); } private void formatIntegerMatrix(Matrix m, StringBuffer s) { analyseIntegerMatrix(m); for (int i = 0; i < m.rows; i++) { for (int j = 0; j < m.cols; j++) { long value = m.valueAt(i, j).longValue(); s.append(formatValue(value)); } if (i != m.rows - 1) { s.append('\n'); } } } private void formatDecimalMatrix(Matrix m, StringBuffer s) { analyseDecimalMatrix(m); for (int i = 0; i < m.rows; i++) { for (int j = 0; j < m.cols; j++) { double v = m.valueAt(i, j).doubleValue(); s.append(formatValue(v)); } if (i != m.rows - 1) { s.append('\n'); } } } private String formatValue(double v) { if (Double.isNaN(v)) { return String.format(specialFormat, NaN); } if (Double.isInfinite(v)) { return String.format(specialFormat, v < 0 ? "-" + Inf : Inf); } StringBuilder s = new StringBuilder(String.format(numberFormat, v)); if (largeExponent) { char sign = s.charAt(s.length()-4); if (sign == '+' || sign == '-') { s.insert(s.length()-3, '0'); } } else if (expFormat) { if (s.charAt(s.length()-4) == '0') { s.deleteCharAt(s.length()-4); } s.insert(0, ' '); } else if (v != 0) { while (s.charAt(s.length()-1) == '0' && s.charAt(s.length()-2) != '.') { s.deleteCharAt(s.length()-1); s.insert(0, ' '); } } return s.toString(); } private String formatValue(long value) { return String.format(numberFormat, value); } private void analyseIntegerMatrix(Matrix m) { long min = m.maxValue().longValue(); long max = m.minValue().longValue(); int digits = Math.max( String.valueOf(min).length(), String.valueOf(max).length()); maxStrLen = digits + 1; if (min >= 0 && signAlwaysShown) { maxStrLen++; } StringBuilder nfmt = new StringBuilder("%"); if (signAlwaysShown) { nfmt.append('+'); } nfmt.append(maxStrLen); nfmt.append('d'); numberFormat = nfmt.toString(); } private void analyseDecimalMatrix(Matrix m) { expFormat = false; largeExponent = false; boolean hasSpecial = false; boolean hasNonZero = false; double minAbsNzVal = Double.MAX_VALUE; double maxAbsNzVal = Double.MIN_VALUE; for (int i = 0; i < m.size; i++) { double value = m.valueAt(i).doubleValue(); if (Double.isNaN(value) || Double.isInfinite(value)) { hasSpecial = true; continue; } if (value != 0.0) { hasNonZero = true; double abs = Math.abs(value); if (abs < minAbsNzVal) { minAbsNzVal = abs; } if (abs > maxAbsNzVal) { maxAbsNzVal = abs; } } } if (hasNonZero) { if (maxAbsNzVal > 1e8) { expFormat = true; } if (!supressSmallValues) { if (minAbsNzVal < 0.0001 || maxAbsNzVal / minAbsNzVal > 1000.0) { expFormat = true; } } } else { maxAbsNzVal = 0.0; minAbsNzVal = 0.0; } int truePrecision = precision; if (expFormat) { maxStrLen = 8 + truePrecision; if ((minAbsNzVal >= 0 && minAbsNzVal < 1e-99) || maxAbsNzVal > 1e100) { largeExponent = true; maxStrLen++; } } else { if (hasNonZero) { String fmt = String.format("%%.%df", precision); truePrecision = maxDigitCount(fmt, m, precision); } else { truePrecision = 0; } truePrecision = Math.min(precision, truePrecision); int intLength = String.valueOf((long) maxAbsNzVal).length(); maxStrLen = intLength + truePrecision + 3; if (hasSpecial) { int maxSpecialLen = Math.max(NaN.length(), Inf.length()+1); maxStrLen = Math.max(maxStrLen, maxSpecialLen); } } // Compute number format StringBuffer nfmt = new StringBuffer("%"); nfmt.append(signAlwaysShown ? "+" : ""); nfmt.append(maxStrLen); nfmt.append('.'); nfmt.append(truePrecision); nfmt.append(expFormat? 'e' : 'f'); numberFormat = nfmt.toString(); // Compute special format StringBuffer sfmt = new StringBuffer("%"); sfmt.append(maxStrLen); sfmt.append('s'); specialFormat = sfmt.toString(); } private static int maxDigitCount(String format, Matrix m, int precision) { int max = 0; for (int i = 0; i < m.size; i++) { double v = m.valueAt(i).doubleValue(); if (!Double.isNaN(v) && !Double.isInfinite(v) && v != 0) { int count = digitCount(format, v, precision); if (count > max) { max = count; } } } return max; } private static int digitCount(String format, double value, int precision) { String s = String.format(format, value); StringBuilder z = new StringBuilder(s); while (z.length() > 0 && z.charAt(z.length()-1) == '0') { z.deleteCharAt(z.length()-1); } return precision - s.length() + z.length(); } }