Преглед изворни кода

new: FormatNumber#asEngineer - use "printf-like" format

Ranides Atterwim пре 10 година
родитељ
комит
855a79c23f

+ 114 - 45
assira/src/main/java/net/ranides/assira/text/FormatNumber.java

@@ -6,7 +6,10 @@
  */
 package net.ranides.assira.text;
 
+import java.util.IllegalFormatConversionException;
 import java.util.Locale;
+import net.ranides.assira.collection.maps.IntHashMap;
+import net.ranides.assira.collection.maps.IntMap;
 
 /**
  *
@@ -18,34 +21,8 @@ public final class FormatNumber {
         /* utility class */ 
     }
     
-    public static class UnitPrefix {
-        
-        private final double v;
-        private final String u;
-
-        UnitPrefix(double v, String u) {
-            this.v = v;
-            this.u = u;
-        }
-
-        public double value() {
-            return v;
-        }
-
-        public String text() {
-            return u;
-        }
-        
-        StringBuilder append(StringBuilder target, int padding, int precision, double value) {
-            return target.append(
-                String.format(Locale.ENGLISH, 
-                    String.format("%%%d.%df%s", padding, precision, text()), value/value())
-            );
-        }
-        
-    }
-    
-    public static final UnitPrefix[] METRIC_PREFIX = new UnitPrefix[]{
+    // metric (SI)
+    private static final UnitPrefix[] MP = new UnitPrefix[]{
         new UnitPrefix(Math.pow(1000, 8),"Y"),
         new UnitPrefix(Math.pow(1000, 7),"Z"),
         new UnitPrefix(Math.pow(1000, 6),"E"),
@@ -65,7 +42,8 @@ public final class FormatNumber {
         new UnitPrefix(Math.pow(1000, -8),"y")
     };
     
-    public static final UnitPrefix[] METRIC_EXP = new UnitPrefix[]{
+    // metric (SI)
+    private static final UnitPrefix[] ME = new UnitPrefix[]{
         new UnitPrefix(Math.pow(1000, 8),"E24"),
         new UnitPrefix(Math.pow(1000, 7),"E21"),
         new UnitPrefix(Math.pow(1000, 6),"E18"),
@@ -85,35 +63,106 @@ public final class FormatNumber {
         new UnitPrefix(Math.pow(1000, -8),"E-24")
     };
     
-    public static final UnitPrefix[] BYTE_PREFIX = new UnitPrefix[]{
-        new UnitPrefix(Math.pow(1024, 8),"Y"),
-        new UnitPrefix(Math.pow(1024, 7),"Z"),
-        new UnitPrefix(Math.pow(1024, 6),"E"),
-        new UnitPrefix(Math.pow(1024, 5),"P"),
-        new UnitPrefix(Math.pow(1024, 4),"T"),
-        new UnitPrefix(Math.pow(1024, 3),"G"),
-        new UnitPrefix(Math.pow(1024, 2),"M"),
-        new UnitPrefix(Math.pow(1024, 1),"K"),
+    // binary: IEC 80000-13
+    private static final UnitPrefix[] BP = new UnitPrefix[]{
+        new UnitPrefix(Math.pow(1024, 8),"Yi"),
+        new UnitPrefix(Math.pow(1024, 7),"Zi"),
+        new UnitPrefix(Math.pow(1024, 6),"Ei"),
+        new UnitPrefix(Math.pow(1024, 5),"Pi"),
+        new UnitPrefix(Math.pow(1024, 4),"Ti"),
+        new UnitPrefix(Math.pow(1024, 3),"Gi"),
+        new UnitPrefix(Math.pow(1024, 2),"Mi"),
+        new UnitPrefix(Math.pow(1024, 1),"Ki"),
+        new UnitPrefix(Math.pow(1024, 0),""),
+    };
+    
+    // binary: exponents
+    private static final UnitPrefix[] BE = new UnitPrefix[]{
+        new UnitPrefix(Math.pow(1024, 8),"B24"),
+        new UnitPrefix(Math.pow(1024, 7),"B21"),
+        new UnitPrefix(Math.pow(1024, 6),"B18"),
+        new UnitPrefix(Math.pow(1024, 5),"B15"),
+        new UnitPrefix(Math.pow(1024, 4),"B12"),
+        new UnitPrefix(Math.pow(1024, 3),"B9"),
+        new UnitPrefix(Math.pow(1024, 2),"B6"),
+        new UnitPrefix(Math.pow(1024, 1),"B3"),
+        new UnitPrefix(Math.pow(1024, 0),"B0"),
+    };
+    
+    // binary: Knuth
+    private static final UnitPrefix[] BK = new UnitPrefix[]{
+        new UnitPrefix(Math.pow(1024, 8),"YY"),
+        new UnitPrefix(Math.pow(1024, 7),"ZZ"),
+        new UnitPrefix(Math.pow(1024, 6),"EE"),
+        new UnitPrefix(Math.pow(1024, 5),"PP"),
+        new UnitPrefix(Math.pow(1024, 4),"TT"),
+        new UnitPrefix(Math.pow(1024, 3),"GG"),
+        new UnitPrefix(Math.pow(1024, 2),"MM"),
+        new UnitPrefix(Math.pow(1024, 1),"KK"),
         new UnitPrefix(Math.pow(1024, 0),""),
     };
+    
+    private static final IntMap<UnitPrefix[]> PREFIXES = new IntHashMap<>(
+        new int[]{ 'M', 'E', 'S', 'B', 'K' },
+        new UnitPrefix[][]{ MP, ME, BP, BE, BK }
+    );
  
-    public static String asEngineer(int padding, int precision, UnitPrefix[] units, double value) {
-        return asEngineer(new StringBuilder(), padding, precision, units, value).toString();
+    /**
+     * Supported formats (unit prefixes): 
+     * <ul>
+     *      <li>M (metric, SI prefix)</li>
+     *      <li>E (metric, exponent)</li>
+     *      <li>S (binary, IEC prefix)</li>
+     *      <li>K (binary, Donald Knuth prefix)</li>
+     *      <li>B (binary, exponent)</li>
+     * </ul>
+     * @param format "printf-compatible" number format, e.g {@code %2.5M}
+     * @param value
+     * @return 
+     */
+    public static String asEngineer(String format, double value) {
+        return asEngineer(new StringBuilder(), format, value).toString();
     }
 
-    public static StringBuilder asEngineer(StringBuilder target, int padding, int precision, UnitPrefix[] units, double value) {
+    /**
+     * Supported formats (unit prefixes): 
+     * <ul>
+     *      <li>M (metric, SI prefix)</li>
+     *      <li>E (metric, exponent)</li>
+     *      <li>S (binary, IEC prefix)</li>
+     *      <li>K (binary, Donald Knuth prefix)</li>
+     *      <li>B (binary, exponent)</li>
+     * </ul>
+     * @param target
+     * @param format format string compatible with String#format. 
+     * @param value
+     * @return 
+     */
+    public static StringBuilder asEngineer(StringBuilder target, String format, double value) {
         if(value < 0) {
             target.append('-');
-            return asEngineer(target, padding, precision, units, -value);
+            return asEngineer(target, format, -value);
+        }
+        
+        char fu = format.charAt(format.length()-1);
+        
+        UnitPrefix[] au = PREFIXES.get(fu);
+        if(null == au) {
+            throw new IllegalFormatConversionException(fu, UnitPrefix.class);
         }
-        UnitPrefix c = units[0];
-        for (UnitPrefix u : units) {
+        
+        UnitPrefix c = au[0];
+        for (UnitPrefix u : au) {
             c = u;
             if (value > c.value()) {
                 break;
             }
         }
-        return c.append(target, padding, precision, value);
+
+        String fs = format.substring(0, format.length()-1)+"f";        
+        return target
+            .append(String.format(Locale.ENGLISH, fs, value/c.value()))
+            .append(c.text());
     }
     
     public static String group(int value) {
@@ -134,5 +183,25 @@ public final class FormatNumber {
         }
         return ret.toString();
     }
+    
+    private static class UnitPrefix {
+        
+        private final double v;
+        private final String u;
+
+        UnitPrefix(double v, String u) {
+            this.v = v;
+            this.u = u;
+        }
+
+        public double value() {
+            return v;
+        }
+
+        public String text() {
+            return u;
+        }
+        
+    }
 
 }

+ 72 - 25
assira/src/test/java/net/ranides/assira/text/FormatNumberTest.java

@@ -6,8 +6,9 @@
  */
 package net.ranides.assira.text;
 
+import java.util.IllegalFormatConversionException;
+import static net.ranides.assira.junit.NewAssert.*;
 import org.junit.Test;
-import static org.junit.Assert.*;
 
 /**
  *
@@ -28,32 +29,78 @@ public class FormatNumberTest {
     }
     
     @Test
-    public void testFormat() {
-        assertEquals("1.20k", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 1200));
-        assertEquals("12.00", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 12));
-        assertEquals("100.00m", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 0.1));
-        assertEquals("120.00m", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 0.12));
-        assertEquals("51.00m", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 0.051));
-        assertEquals("5.10m", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 0.0051));
-        assertEquals("510.00μ", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_PREFIX, 0.00051));
+    public void testAsEngineer() {
+        assertEquals("1.20k", FormatNumber.asEngineer("%.2M", 1200));
+        assertEquals("12.00", FormatNumber.asEngineer("%.2M", 12));
+        assertEquals("100.00m", FormatNumber.asEngineer("%.2M", 0.1));
+        assertEquals("120.00m", FormatNumber.asEngineer("%.2M", 0.12));
+        assertEquals("51.00m", FormatNumber.asEngineer("%.2M", 0.051));
+        assertEquals("5.10m", FormatNumber.asEngineer("%.2M", 0.0051));
+        assertEquals("510.00μ", FormatNumber.asEngineer("%.2M", 0.00051));
         
-        assertEquals("1.20E3", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 1200));
-        assertEquals("12.00E0", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 12));
-        assertEquals("100.00E-3", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 0.1));
-        assertEquals("120.00E-3", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 0.12));
-        assertEquals("51.00E-3", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 0.051));
-        assertEquals("5.10E-3", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 0.0051));
-        assertEquals("510.00E-6", FormatNumber.asEngineer(1, 2, FormatNumber.METRIC_EXP, 0.00051));
+        assertEquals("1.20E3", FormatNumber.asEngineer("%.2E", 1200));
+        assertEquals("12.00E0", FormatNumber.asEngineer("%.2E", 12));
+        assertEquals("100.00E-3", FormatNumber.asEngineer("%.2E", 0.1));
+        assertEquals("120.00E-3", FormatNumber.asEngineer("%.2E", 0.12));
+        assertEquals("51.00E-3", FormatNumber.asEngineer("%.2E", 0.051));
+        assertEquals("5.10E-3", FormatNumber.asEngineer("%.2E", 0.0051));
+        assertEquals("510.00E-6", FormatNumber.asEngineer("%.2E", 0.00051));
         
-        assertEquals("12.53M", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 12.527*1024*1024));
-        assertEquals("11.72K", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 12000));
-        assertEquals("1.17K", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 1200));
-        assertEquals("12.00", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 12));
-        assertEquals("0.10", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 0.1));
-        assertEquals("0.12", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 0.12));
-        assertEquals("0.05", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 0.051));
-        assertEquals("0.01", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 0.0051));
-        assertEquals("0.00", FormatNumber.asEngineer(1, 2, FormatNumber.BYTE_PREFIX, 0.00051));
+        assertThrows(IllegalFormatConversionException.class, ()->{
+            FormatNumber.asEngineer("%.2f", 0.00051);
+        });
     }
     
+    @Test
+    public void testAsEngineerBin() {
+        assertEquals("12.53Mi", FormatNumber.asEngineer("%.2S", 12.527*1024*1024));
+        assertEquals("11.72Ki", FormatNumber.asEngineer("%.2S", 12000));
+        assertEquals("1.17Ki", FormatNumber.asEngineer("%.2S", 1200));
+        assertEquals("12.00", FormatNumber.asEngineer("%.2S", 12));
+        assertEquals("0.10", FormatNumber.asEngineer("%.2S", 0.1));
+        assertEquals("0.12", FormatNumber.asEngineer("%.2S", 0.12));
+        assertEquals("0.05", FormatNumber.asEngineer("%.2S", 0.051));
+        assertEquals("0.01", FormatNumber.asEngineer("%.2S", 0.0051));
+        assertEquals("0.00", FormatNumber.asEngineer("%.2S", 0.00051));
+        
+        assertEquals("12.53B6", FormatNumber.asEngineer("%.2B", 12.527*1024*1024));
+        assertEquals("11.72B3", FormatNumber.asEngineer("%.2B", 12000));
+        assertEquals("1.17B3", FormatNumber.asEngineer("%.2B", 1200));
+        assertEquals("12.00B0", FormatNumber.asEngineer("%.2B", 12));
+        assertEquals("0.01B0", FormatNumber.asEngineer("%.2B", 0.0051));
+        assertEquals("0.00B0", FormatNumber.asEngineer("%.2B", 0.00051));
+        assertEquals("0B0", FormatNumber.asEngineer("%.0B", 0.00051));
+        assertEquals("72B6", FormatNumber.asEngineer("%.0B", 72*1024*1024));
+        assertEquals("12B9", FormatNumber.asEngineer("%.0B", 12*1024*1024*1024L));
+        assertEquals("12B12", FormatNumber.asEngineer("%.0B", 12*1024L*1024L*1024L*1024L));
+        
+    }
+    
+    @Test
+    public void testAsEngineerKnuth() {
+        assertEquals("12.53MM", FormatNumber.asEngineer("%.2K", 12.527*1024*1024));
+        assertEquals("11.72KK", FormatNumber.asEngineer("%.2K", 12000));
+        assertEquals("1.17KK", FormatNumber.asEngineer("%.2K", 1200));
+        assertEquals("12.00", FormatNumber.asEngineer("%.2K", 12));
+        assertEquals("0.01", FormatNumber.asEngineer("%.2K", 0.0051));
+        assertEquals("0.00", FormatNumber.asEngineer("%.2K", 0.00051));
+        assertEquals("0", FormatNumber.asEngineer("%.0K", 0.00051));
+        assertEquals("72MM", FormatNumber.asEngineer("%.0K", 72*1024*1024));
+        assertEquals("12GG", FormatNumber.asEngineer("%.0K", 12*1024*1024*1024L));
+        assertEquals("12TT", FormatNumber.asEngineer("%.0K", 12*1024L*1024L*1024L*1024L));
+    }
+    
+    @Test
+    public void testAsEngineerFormat() {
+        assertEquals("001.27k", FormatNumber.asEngineer("%06.2M", 1273));
+        assertEquals("01.273k", FormatNumber.asEngineer("%06.3M", 1273));
+        assertEquals("1.273000k", FormatNumber.asEngineer("%06M", 1273));
+        assertEquals("001.273000k", FormatNumber.asEngineer("%010M", 1273));
+        
+        assertEquals("1.273000k", FormatNumber.asEngineer("%M", 1273));
+        assertEquals("1.273000E3", FormatNumber.asEngineer("%E", 1273));
+        assertEquals("1.500000Ki", FormatNumber.asEngineer("%S", 1536));
+    }
+        
+    
 }