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());
}
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.