3

I need to make sure that all my decimal numbers are always at most 15 characters long (including the dot), while keeping as much precision as possible. So, it must be 15 characters at maximum, including the "." the "E" for scientific notation and "-".

What I was thinking is to use scientific notation for large numbers, and use rounding for small numbers.

For example, for 1234567891234567.123456789 I would use scientific notation, but for 0.123456789123456789 I would just round it.

I looked for java libraries that would do this for me but I could not find any that lets me specify the total number of characters for the representation.

Grateful for any suggestions or pointers.

EDIT: some more thoughts - a number such as 0.000000000000024 can be represented with no loss by using 24000E-18. While 0.123456789123424 for example has to suffer some loss, which of course makes it harder to write any sort of simple branching algo.

EDIT2: the format that we use to the transmit data is alpha-numeric and limits the data to 15 characters total. I have to write code to satisfy the format so the data can be transmitted without triggering format errors, but while still keeping max precision possible within the limitation.

EDIT3: I am using this to test my functions, but so far everything fails for a number of cases:

  Random rnd = new Random();
  double n = 100000 + rnd.nextDouble() * 900000;
  String res;
  double rangeMin = -123456789123456D;
  double rangeMax = 123456789123456D;
  String val;
  for (int i=1;i<=1000;i++) {
    n = rangeMin + (rangeMax - rangeMin) * rnd.nextDouble();
    val = Double.toString(n);
    res = shorteningFunction(val);
    System.out.println(val + " " + val.length() + " " + res + " " + res.length());
  }
delica
  • 1,647
  • 13
  • 17
  • You should use `BigDecimal` and then set the precision you want. – Tim Biegeleisen Jan 10 '20 at 10:32
  • @TimBiegeleisen precision does not seem to deal with characters, rather with digits. Maybe i am missing it but I do not see how I can limit the total number of characters? Would you mind re-opening my question, this does not seem to answer it? – delica Jan 10 '20 at 10:40
  • Isn't a digit a single character? – Tim Biegeleisen Jan 10 '20 at 10:45
  • @TimBiegeleisen indeed it is, but it does not count the dot character, or the "E" when using scientific notation. – delica Jan 10 '20 at 10:48
  • It makes no sense to have this requirement. Wouldn't it mean that not all numbers would be stored with the same precision? Would you really want that? – Tim Biegeleisen Jan 10 '20 at 10:49
  • @TimBiegeleisen the format that we use to the transmit data is alpha-numeric and limits the data to 15 characters total. I have to write code to satisfy the format so the data can be transmitted without triggering format errors, but while still keeping max precision possible within the limitation. Thank you for re-opening! – delica Jan 10 '20 at 10:51
  • Would `0.123456789123456789` be 'shortened' to `0.1234567891235` or `.12345678912346`? – Kevin Cruijssen Jan 10 '20 at 11:39
  • @KevinCruijssen to 0.1234567891235 – delica Jan 10 '20 at 13:46
  • **`Formatter` with `%15g`** does what you describe -- decimal-with-point format like `%f` when that is optimal and 'scientific' format like `%e` otherwise. This follows the practice of `%[w[.d]]g` in C (and C++) `*printf` which in turn followed the practice of `Gw[.d]` in FORTRAN back to the 1960s. – dave_thompson_085 Jan 14 '20 at 15:36
  • @dave_thompson_085 I just tried `System.out.printf("%15g",0.123456789123456789);` it printed `0.123457` – Scratte Jan 14 '20 at 17:44
  • @Scratte: I missed the default precision is inappropriate here so we need `%15.15g`. More importantly I forgot Java uses the C handling of overflow (expands) not the FORTRAN (hard limit) so it doesn't do quite what OP wants, although adding a `substring` might come close enough. Remember Java `double` (and nearly all other computer FP nowadays) only supports 15-and-a-fraction decimal digits of precision under any circumstance; for more you need `BigDecimal`. – dave_thompson_085 Jan 15 '20 at 01:52
  • @dave_thompson_085 I had to use `%13.13g` to keep the entire output at 15 char, but unfortunately it didn't work with the number `-1234567891234567.123456789` where `e+15` makes it 19 char. I wrote a solution where the precision is modified according to an initial string result. – Scratte Jan 15 '20 at 02:06

2 Answers2

1

I didn't want code posted that's not working, so I've removed the clutter.

This is the idea in pseudocode:

  1. Turn it into a String.
  2. Check the length
  3. Loop:
    • If the length is too long:
    • Remove presicion.
    • Turn it into a String.
    • Check the length.
  4. Return
Scratte
  • 3,056
  • 6
  • 19
  • 26
  • Nice answer, but `-123456789123456` results in `-1.2345678912346E+14` (length 20). – Kevin Cruijssen Jan 15 '20 at 09:49
  • Indeed that's a good starting point, thank you! 0.00542222222299 goes through with no changes (16 chars). – delica Jan 15 '20 at 10:37
  • I provided a testing function in the edit I just made. – delica Jan 15 '20 at 10:58
  • @delica I have a question. What normally makes a answer accepted? What normally makes an answer to be marked as useful? – Scratte Jan 15 '20 at 19:39
  • 1
    I would say if it works, then it is accepted as answer. If it doesn't work it doesn't make sense to accept since others will think it's solved and not post any more answers. – delica Jan 15 '20 at 20:05
1

One can make use of BigDecimal, and for the integer part BigInteger.

/**
 * @param num number representation.
 * @param max the maximal length the result should have.
 * @return
 */
public static String truncateNumber(String num, int max) {
    num = num.replaceFirst("\\.0*$", "");
    BigDecimal x = new BigDecimal(num);

    // Large numbers - integral part
    String bigI = x.toBigInteger().toString();
    if (bigI.length() > max) {
        int expon10 = bigI.length() - max - 1; // - 1 for E

        // Digits after E:
        if (expon10 == 0) {
            ++expon10;
        } else {
            for (int p = expon10; p > 0; ++p) {
                ++expon10;
                p /= 10;
            }
        }
        x = x.movePointLeft(expon10);
        String plain = x.toPlainString().substring(0, max - 1 - expon10);
        return plain + "E" + expon10;
    }

    // Tiny numbers - 0.000 (as E-1 already requires 3 positions)
    String reprP = x.toPlainString();
    if (reprP.startsWith("-0.00")) {
        return truncateNumber(num.substring(1), max - 1);
    } else if (reprP.startsWith("0.00")) {
        String reprE = x.toEngineeringString(); // Does most work.
        int epos = reprE.indexOf('E');
        String mantissa = reprE.substring(0, epos);
        String exp = reprE.substring(epos);
        return mantissa.substring(0, Math.min(epos, max - exp.length())) + exp;
    }

    // Normal range - assumed in format 123.456, integral part in range
    String simple = x.toPlainString();
    if (simple.length() > max) {
        simple = simple.substring(0, max).replaceFirst("\\.0*$", "");
    }
    return simple;
}

That can probably written more nicely, substrings ending on \.0*, especially there is some repetitive usage of toPlainString and such. Also a too smal max will be harmful.

Whether num maybe given in scientific / engineering notation is also open.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 1
    This works pretty well, except that often it will shorten too much: 5.273913272867031E12 -> 5273913272867 (from 20 to 13 chars for example.) – delica Jan 24 '20 at 14:07
  • @delica good to know, but I leave solving the exact solution to you. Especially as you are deeper into the matter. Good luck – Joop Eggen Jan 24 '20 at 17:47
  • Thank you for the partial solution! Appreciate it! – delica Jan 24 '20 at 19:28