package com.stellarsoftware.beam; import java.awt.*; import java.text.NumberFormat; import java.text.DecimalFormat; import java.util.*; // Locale, ArrayList /// /// To Do: unify suckDouble -- parseDouble /// also: use private NumberFormat(Locale=US) on input /// private NumberFormat & DecimalFormat are available. /// also: filter through nationalize() before parsing inputs /// /** U.java static utilities class * contains a "main" driver to test formatter fmt() * * * @author M.Lampton (c) STELLAR SOFTWARE 2004 all rights reserved. */ class U implements B4constants { public static void main(String[] cmd) // testing { double nums[] = {1.234, -5.678, +0.432e-4, -0.23456e-7}; double huge = 1234567.89; for (int j=0; j<4; j++) for (int i=0; i<16; i++) // check e-format lengths { int seeklength = i; int seekdec = 4; String s = fmtc(nums[j], seeklength, seekdec, 'e'); int obslen = s.length(); } } //-------public static methods----------- static int getTwoDigitCode(String s) // Ignores the first character and returns the two digit code: "X34" => 34 // Returns zero if there is no such code { if (s.length() < 2) return 0; char a = getCharAt(s,1); // always safe if ((a<'0') || (a>'9')) return 0; int i = a-48; char b = getCharAt(s,2); // always safe if ((b<'0') || (b>'9')) return i; return i*10 + b-48; } public static String getExtension(String fname) // includes the dot. { try {return fname.substring(fname.lastIndexOf(".")); } catch (Exception e) {return ""; } } public static String removeExtension(String fname) // deletes the dot. { if (fname == null) return null; int index = fname.lastIndexOf("."); if (index > 2) return fname.substring(0, index); else return fname; } public static String getOnlyPath(String fullname) // deletes the filename and extension from fpath. // gives the directory path without final slash. { if (fullname == null) return null; int index = fullname.lastIndexOf("/"); if (index > 2) return fullname.substring(0, index); else return fullname; } static int getColorCode(char c) // allows artwork to convert a tag char into a display color { int icolor = BLACK; // zero; others are 1...9 switch (c) { case 'R': case 'r': icolor = RED; break; case 'G': case 'g': icolor = GREEN; break; case 'Y': case 'y': icolor = YELLOW; break; case 'B': case 'b': icolor = BLUE; break; case 'M': case 'm': icolor = MAGENTA; break; case 'C': case 'c': icolor = CYAN; break; default: icolor = BLACK; } return icolor; } static int getColorCode(String s) { char c = U.getCharAt(s,0); // safe even for empty strings return getColorCode(c); } static int getInt(double x) { return (int) Math.round(x); } static double grand() // Returns a zero-mean unit-variance Gaussian random number { double sum = -6.0; for (int i=0; i<12; i++) sum += Math.random(); return sum; } static double put360(double x) // Puts x into range 0<=x<360 { double y = x/360 - 0.5; return 360*(y-Math.round(y)+0.5); } static double put180(double x) // Puts x into range -180<x<+180 { double y = x/360; return 360*(y-Math.round(y)); } static int minmax(int i, int bot, int top) { return Math.max(bot, Math.min(top, i)); } static double minmax(double d, double bot, double top) { return Math.max(bot, Math.min(top, d)); } static double trapezoid(double x) // height=1; center is x=0; shoulders x=+-0.25; base x=+-0.5 { return minmax(2.0-4.0*Math.abs(x), 0.0, 1.0); } static double getBlue(double x) // convert 0<x<1 to RGB thermometer: blue peaks at 0.0 { return trapezoid(x); } static double getGreen(double x) // convert 0<x<1 to RGB thermometer: green peaks at 0.5 { return trapezoid(x-0.5); } static double getRed(double x) // convert 0<x<1 to RGB thermometer: red peaks at 1.0 { return trapezoid(x-1); } static void beep() // sounds more like a bonk. { Toolkit.getDefaultToolkit().beep(); } static String getExtensionToLower(String fname) { String ext = null; int i = fname.lastIndexOf('.'); if (i > 0 && i < fname.length() - 1) ext = fname.substring(i+1).toLowerCase(); return ext; } static double pm1(double x) { return Math.min(Math.max(x, -1.0), +1.0); } static boolean isNegZero(double x) { return 1.0/x == Double.NEGATIVE_INFINITY; } static double sqr(double x) { return x*x; } static double sawtooth(double x, double p, boolean bOdd) { if (p<TOL) return 0.0; if (!bOdd) x += 0.5*p; return x - p*Math.round(x/p); } static double cosd(double deg) { return Math.cos(Math.toRadians(deg)); } static double sind(double deg) { return Math.sin(Math.toRadians(deg)); } static double tand(double deg) { return Math.tan(Math.toRadians(deg)); } static int imax3(int a, int b, int c) { return Math.max(a, Math.max(b, c)); } static int imax5(int a, int b, int c, int d, int e) { return Math.max(a, Math.max(b, Math.max(c, Math.max(d, e)))); } static boolean isOdd(int i) { return i % 2 != 0; } static boolean isBit(int word, int whichbit) { return isOdd(word >>> whichbit); } static int setBit(int word, int whichbit) // forces a given bit to be "1" { if (isBit(word, whichbit)) return word; // already set return word + (1 << whichbit); } static int clearBit(int word, int whichbit) // forces a given bit to be "0" { if (isBit(word, whichbit)) word -= (1 << whichbit); return word; } static int manageBit(int word, int whichbit, boolean b) // forces a given bit to be "b" { return b ? setBit(word, whichbit) : clearBit(word, whichbit); } static int SAFEINT(double x) { return (int) Math.max(-10000.0, Math.min(10000.0, x)); } static String fwi(int n, int w) // converts an int to a string with given width. { StringBuffer sb = new StringBuffer(); sb.append(Integer.toString(n)); while (sb.length() < w) sb.insert(0, " "); return sb.toString(); } static String fn6(int dat[], int ndat) { StringBuffer sb = new StringBuffer(100); for (int i=0; i<ndat; i++) sb.append(fwi(dat[i], 6)); return sb.toString(); } static String fwd(double x, int w, int d) // converts a double to a string with given width and decimals. { NumberFormat nf = NumberFormat.getInstance(Locale.US); DecimalFormat df = (DecimalFormat) nf; df.setMaximumFractionDigits(d); df.setMinimumFractionDigits(d); df.setGroupingUsed(false); String s = df.format(x); while (s.length() < w) s = " " + s; if (s.length() > w) { s = ""; for (int i=0; i<w; i++) s = s + "-"; } return s; } static String tidy(double x) // Converts double to a tidy string; // trims trailing zeros, leading & trailing blanks // but fails to tidy 0.99999999999 { StringBuffer sb = new StringBuffer(fwd(x, 18, 9)); int iend = sb.length()-1; int i; for (i=iend; i>4; i--) { if ('0' != sb.charAt(i)) break; } sb.setLength(i+1); String s = sb.toString(); return s.trim(); } static String gd(double x) // Converts a double into a nicely formatted string. // Sacrifices precision to gain shortness. // Uses D notation if reasonable size // else uses E notation. Three to 7 chars. { String s = "0.00"; if (x == 0.0) return s; int mag = (int) log10(Math.abs(x)); if ((mag<-3) || (mag>3)) return fweshort(x).trim(); boolean bNeg = x < 0.0; int ndec = 5 - Math.max(0, mag); if (bNeg) ndec--; StringBuffer sb = new StringBuffer(fwd(x, 8, ndec)); while ((sb.length() > 2) && (sb.charAt(sb.length()-1)=='0')) sb.setLength(sb.length()-1); String t = new String(sb); return t.trim(); } static String fwe(double x) // Converts a positive double into a 9-char Enotation string // But what about negative exponents? // Ugly property: shows E-03 not E-3 { NumberFormat nf = NumberFormat.getInstance(Locale.US); DecimalFormat ef = (DecimalFormat) nf; ef.applyPattern("0.00E00"); String s = ef.format(x); if (s.length()==7) // when zero return " "+s; if (s.length()==8) // when positive return " "+s; return s; // else negative } static String fweshort(double x) // a shorter version of fwe() { NumberFormat nf = NumberFormat.getInstance(Locale.US); DecimalFormat ef = (DecimalFormat) nf; ef.applyPattern("0.00E0"); String s = ef.format(x); return s; // else negative } static String twoint(int i, int j) // converts two small ints to a string of width 8 { return fwi(i,4) + fwi(j,4); } static String nd(int n, double[] v) // converts a vector of n doubles into a fixed point string { StringBuffer sb = new StringBuffer(100); for (int i=0; i<n; i++) sb.append(fwd(v[i],11,6)); String s = sb.toString(); return s; } public static String fmtc(double x, int fw, int dp, char c) /// formats to either fwidth+enotation or fwidth+decplaces. /// char c must be 'e', 'E', ',' or '.' /// Called by EPanel:putFieldDouble() { if (fw < 1) return ""; if ((c=='E') || (c=='e')) return efmt(x, fw, c); else return ffmt(x, fw, dp, c); } public static String efmt(double x, int fw, char c) // char c will be 'e' or 'E' { //---------first design the format--------- if (fw < 7) { String s = "------"; String t = s.substring(0, fw); return t; } char sign = '+'; if (x < 0) { sign = '-'; x = Math.abs(x); } if (x < 1E-99) x = 0.0; if (x > 9E99) x = 9E99; if (fw > 40) fw = 40; int eminus = 0; // extra char for expon minus sign if (x < 1.0) eminus = 1; StringBuffer sb = new StringBuffer(50); sb.append("0."); for (int i=3; i<fw-3-eminus; i++) sb.append("0"); sb.append("E00"); // never get plus signs in exponentials //---------now construct the formatter---------- NumberFormat nf = NumberFormat.getInstance(Locale.US); DecimalFormat df = (DecimalFormat) nf; df.applyPattern(sb.toString()); //----re-use sb--------------- sb.setLength(0); sb.append(sign); sb.append(df.format(x)); //-----if decimal point is absent, correct it----- if (getCharAt(sb, 2) == 'E') // safe sb.insert(2, '.'); if (c=='e') for (int i=0; i<sb.length(); i++) { if (sb.charAt(i) == 'E') sb.setCharAt(i, 'e'); } return sb.toString(); } public static String ffmt(double x, int w, int d, char c) { if (d<0) // digits d = 0; if (w<1) // width w = 1; NumberFormat nf = NumberFormat.getInstance(Locale.US); DecimalFormat df = (DecimalFormat) nf; df.setMaximumFractionDigits(d); df.setMinimumFractionDigits(d); df.setGroupingUsed(false); StringBuffer sb = new StringBuffer(50); sb.append(df.format(x)); while (sb.length() < w) sb.insert(0, ' '); // prepend sb if (sb.length() > w) { int frontsize = sb.length() - 1 - d; if (frontsize < w) sb.setLength(w); // meat ax. else { sb.setLength(0); // clears sb for (int i=0; i<w; i++) sb.append('-'); } } if (c==',') for (int i=0; i<sb.length(); i++) { if (sb.charAt(i)=='.') sb.setCharAt(i, ','); } return sb.toString(); } public static int parseInt(String s) // exception safe parser; handles "?" strings ok { try { int i = Integer.parseInt(s); return i; } catch(NumberFormatException nfe) { return -0; } } /******************** public static double parseDouble(String s) // exception safe parser; handles "?" strings ok { try { double d = Double.parseDouble(s); return d; } catch(NumberFormatException nfe) { return -0.0; } } *********************/ public static int suckInt(String s) // Pulls the first int from a string. // The int may be preceded or followed by blanks, text, etc. // Failure: returns zero. { int istart=0, iend=0; int ilen = s.length(); if (ilen < 1) return 0; int i=0; while ((i<ilen) && (getCharAt(s,i)<'0') || (getCharAt(s,i)>'9')) istart = iend = ++i; while ((i<ilen) && ('0'<=getCharAt(s,i)) && (getCharAt(s,i)<='9')) iend = ++i; if (iend <= istart) return 0; String t = s.substring(istart, iend).trim(); return Integer.parseInt(t); } public static double suckDouble(String s) // pulls a double out of a string, trimmed or not. // String may have preceding and trailing blanks. // String may NOT have preceding and trailing chars. Tests OK. // if there is a final colon it is trimmed off. Tests OK. // Malformed? returns NaN. // EMPTY? returns -0.0. // Contains -0.0? returns +0.0. { s = nationalize(s); // trim, remove groups, convert comma to a period. int len = s.length(); if (len < 1) return -0.0; if (getCharAt(s, len-1) == ':') s = s.substring(0, len-1); // drop the tag char. s = s.trim(); len = s.length(); if (len < 1) return -0.0; double d = Double.NaN; try { d = Double.parseDouble(s); } catch (NumberFormatException nfe) { if (nfe.getMessage().equals("EMPTY String")) return -0.0; return Double.NaN; } if (isNegZero(d)) d = +0.0; return d; } public static char suckChar(String s) { return getCharAt(s, 0); } public static char getCharAt(String s, int n) // Safe version of String.charAt() // error condition returns ' ' { if ((n < 0) || (s.length() <= n)) return ' '; char c = ' '; try {c = s.charAt(n);} catch (IndexOutOfBoundsException iobe) {c = ' ';} return c; } public static char getCharAt(StringBuffer sb, int n) { String s = new String(sb); return getCharAt(s, n); } public static String s12(String s) // makes any string at least 12 characters long { while (s.length() < 10) s += ' '; return s; } static double log10(double arg) { if (arg>0.0) return Math.log(arg)/LN10; return -999.9; } static double dex(double arg) { if (arg > 300.0) arg = 300.0; if (arg < -300.0) arg = -300.0; return Math.pow(10.0, arg); } static int tokenize(String input, String delimiters, ArrayList<String> tList) { tList.clear(); StringTokenizer st = new StringTokenizer(input, delimiters); int tcount = 0; while (st.hasMoreTokens()) { tList.add(st.nextToken().trim()); tcount++; } return tcount; } static void ruler(double a, double b, // the given interval boolean bBeyond, // overspan? else underspan double[] values, // list of tick values int[] output) // [0]=nticks,[1]=fracdigits. // Paul Heckbert in GRAPHICS GEMS 1990; Lampton 2005. { if (a==b) return; if (a>b) {double t=a; a=b; b=t;} // following choices give nTicks=2 to 5, beyond or not. int nomticks = bBeyond ? 3 : 5; double range = nicenum(b-a, false); double step = nicenum(range/(nomticks-1), true); if (bBeyond) { a = Math.floor(a/step)*step; b = Math.ceil(b/step)*step; } else { a = Math.ceil(a/step)*step; b = Math.floor(b/step)*step; } int iDig = -(int) Math.floor(log10(step)); int nFracDigits = Math.max(iDig, 0); int nTicks = (int) (1.5 + (b-a)/step); nTicks = Math.max(1, Math.min(MAXTICKS-1, nTicks)); for (int i=0; i<nTicks; i++) values[i] = a+i*step; // in Constants.java: NTICKS=0; NFRACDIGITS=1, MAXTICKS=10 output[NTICKS] = nTicks; output[NFRACDIGITS] = nFracDigits; } static double nicenum (double x, boolean bRound) // finds a nice number approx equal to |x|. // rounds downward if bRound=true, else nice>=|x|. // Paul Heckbert in GRAPHICS GEMS 1990. { double exp; // exponent of x in e-notation double f; // fractional part of x double nf; // nice fraction x = Math.abs(x); if (x==0.0) return 0.0; exp = Math.floor(log10(x)); f = x / dex(exp); if (bRound) if (f<1.5) nf=1.0; else if (f<3.0) nf=2.0; else if (f<7.0) nf=5.0; else nf=10.0; else if (f<=1.0) nf=1.0; else if (f<=2.0) nf=2.0; else if (f<=5.0) nf=5.0; else nf=10.0; return nf * dex(exp); } /********************* static void ruler(double a, double b, // the given interval boolean beyond, // overspan? else underspan double[] values, // list of output tick values int[] output) // 0=NTICKS; 1=NFRACDIGITS // Lampton's ruler algorithm, span within or beyond (a,b). // Gives 2 to 4 ticks with the coefficients shown. // Subtle bug though: sometimes intervals are unequal! { double coefbeyond=1.0, coefwithin=0.333; //0.5; output[NTICKS] = output[NFRACDIGITS] = 0; if (a==b) return; if (a>b) {double t=a; a=b; b=t;} double f = (b-a) * (beyond ? coefbeyond : coefwithin); double p = Math.floor(log10(f)); double step = dex(p); // enlarge step=N*10^M to fit the span f /= step; if (f>8.0) step *= 8.0; else if (f>5.0) step *= 5.0; else if (f>4.0) step *= 4.0; else if (f>3.0) step *= 3.0; else if (f>2.0) step *= 2.0; else if (f>1.5) step *= 1.5; int i = (int) (beyond ? Math.floor(a/step) : Math.ceil(a/step)); int j = (int) (beyond ? Math.ceil(b/step) : Math.floor(b/step)); // array overflow safety limiter: if (j>i+6) j = i+6; for (int k=0; k<=j-i; k++) values[k] = (k+i)*step; output[NTICKS] = 1+j-i; output[NFRACDIGITS] = (int) (p<0 ? -p : 0); // A test to learn about the ruler bug: // Sometimes, segments are unequal: -2, 0, 2, 3. // Are the ticks equally spaced? // Hmmm, can't get bug to reappear. double delta = values[1] - values[0]; int nticks = output[NTICKS]; for (int n=2; n<nticks; n++) { double d = values[n] - values[n-1]; double diff = d - delta; } } ********************/ public static boolean areSimilar(int i, int j) // same type? // returns true if ray attributes i & j are both spacelike // or both angular, else false. { // return (i/3)%2 == (j/3)%2; boolean biXYZ = ((i==RX) || (i==RY) || (i==RZ) || (i==RTXL) || (i==RTYL) || (i==RTZL)); boolean biUVW = ((i==RU) || (i==RV) || (i==RW) || (i==RTUL) || (i==RTVL) || (i==RTWL)); boolean bjXYZ = ((j==RX) || (j==RY) || (j==RZ) || (j==RTXL) || (j==RTYL) || (j==RTZL)); boolean bjUVW = ((j==RU) || (j==RV) || (j==RW) || (j==RTUL) || (j==RTVL) || (j==RTWL)); return (biXYZ && bjXYZ) || (biUVW && bjUVW); } public static boolean isAngle(int i) // Returns true if surface var is tilt, pitch, or roll { return ((i==OTILT) || (i==OPITCH) || (i==OROLL)); } public static boolean getBit(int arg, int whichbit) { arg >>= whichbit; return (arg & 1) == 1; } public static double dGetDefaultValue(String s) // Used within RT13 to evaluate default ray strings { s = s.trim(); if (s.equals("-makeup")) return MMAKEUP; // -1.000001 if (s.equals("+makeup")) return PMAKEUP; // +1.000001 try { double d = Double.parseDouble(s); return d; } catch(NumberFormatException nfe) { return -0.0; } } /** N-dimensional fast Fourier transform using zero-based arrays. * Cooley-Tukey FFT algorithm; see Press et al Numerical Recipes 1986. * data[0,1,2...] = {real0, imag0, real1, imag1, ....} in and out. * ndim is how many dimensions, = 1 for 1-dimensional transform. * nn[idim] is how many complex points in each dimension. * Each nn must be an exact power of two. * * @author M.Lampton Java edition (c) 2002 STELLAR SOFTWARE */ static public void fourn(double data[], int nn[], int ndim, int isign) { int idim; int i1, i2, i3, i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2; int ibit, k1, k2, n, nprev, nrem, ntot; double tempi, tempr; double theta, wi, wpi, wpr, wr, wtemp; for (ntot=1, idim=0; idim<ndim; idim++) ntot *= nn[idim]; nprev=1; for (idim=ndim-1; idim>=0; idim--) { n = nn[idim]; nrem = ntot/(n*nprev); ip1 = nprev*2; ip2 = ip1*n; ip3 = ip2*nrem; i2rev = 0; for (i2=0; i2<ip2; i2+=ip1) { if (i2 < i2rev) { for (i1=i2; i1<=i2+ip1-2; i1+=2) { for (i3=i1; i3<ip3; i3+=ip2) { i3rev = i2rev+i3-i2; double t = data[i3]; data[i3] = data[i3rev]; data[i3rev] = t; t = data[i3+1]; data[i3+1] = data[i3rev+1]; data[i3rev+1] = t; } } } ibit = ip2/2; while (ibit>=ip1 && i2rev>=ibit) { i2rev -= ibit; ibit /= 2; } i2rev += ibit; } ifp1=ip1; while (ifp1 < ip2) { ifp2 = ifp1*2; theta = isign*2*3.14159265358979324/(ifp2/ip1); wtemp = Math.sin(0.5*theta); wpr = -2.0*wtemp*wtemp; wpi = Math.sin(theta); wr = 1.0; wi=0.0; for (i3=0; i3<ifp1; i3+=ip1) { for (i1=i3; i1<=i3+ip1-2; i1+=2) { for (i2=i1; i2<ip3; i2+=ifp2) { k1 = i2; k2 = k1+ifp1; tempr = (float)wr*data[k2]-(float)wi*data[k2+1]; tempi = (float)wr*data[k2+1]+(float)wi*data[k2]; data[k2] = data[k1]-tempr; data[k2+1] = data[k1+1]-tempi; data[k1] += tempr; data[k1+1] += tempi; } } wr = (wtemp=wr)*wpr-wi*wpi+wr; wi = wi*wpr+wtemp*wpi+wi; } ifp1 = ifp2; } nprev *= n; } if (isign < 0) for (i1=0; i1<2*ntot; i1++) data[i1] /= ntot; } //----private static method for nationalization------------ static String nationalize(String s) // Nationalizes a numerical string for worldwide input. // Eliminates groupings, enforces period as decimal point { StringBuffer sbIn = new StringBuffer(s.trim()); int len = sbIn.length(); //---convert to periods and locate rightmost-------- int dpcount = 0; // 0 = not found int jdp = -1; // -1 = not found for (int i=0; i<len; i++) { char c = sbIn.charAt(i); if ((c=='.') || (c==',')) { dpcount++; jdp = i; sbIn.setCharAt(i, '.'); } } if (dpcount < 2) return sbIn.toString(); //---copy, skipping group chars, but keep jdp----- StringBuffer sbOut = new StringBuffer(); for (int i=0; i<len; i++) { char c = sbIn.charAt(i); if ((c != '.') || (i==jdp)) sbOut.append(c); } return sbOut.toString(); } public static int getFields(String ruler, String record, ArrayList<String> fList, ArrayList<Character> cList) // Reads ruler; extract fields from table record into fList. // NOTE initial colon is not a field, and is skipped. // Returns the number of fields found including zero length fields. // (This should equal ncolons+1.) // Also returns the list of tag characters. // Unused in BEAM FOUR, but elegant. // StringBuilder is absent in Java 1.4; my ProGuard uses 1.4; // use safer/slower StringBuffer instead for ProGuard compatibility. { fList.clear(); cList.clear(); int max = Math.max(ruler.length(), record.length()); // StringBuilder sb = new StringBuilder(); StringBuffer sb = new StringBuffer(); boolean bInitialColon = (U.getCharAt(ruler, 0) == ':'); int istart = bInitialColon ? 1 : 0; for (int i=istart; i<max; i++) { if (U.getCharAt(ruler, i) == ':') { fList.add(sb.toString().trim()); cList.add(U.getCharAt(record, i)); sb.setLength(0); } else sb.append(U.getCharAt(record, i)); } fList.add(sb.toString().trim()); cList.add(' '); return fList.size(); } }