0

I got a method which takes a positive or negative int of 1 - 8 digits as input and converts every digit into its corresponding ASCII value while preserving the order of the digits. I want it to be as memory and time efficient as possible, so I want to avoid unnecessary loops, temporary variables and reference data types.

Example, see table below with ASCII values for each digit:
input: int value = 185
output: byte[] byteArray = [49, 56, 53]

Digit ASCII-value
1 49
8 56
5 53

What I currently have is this naive method where I first have to convert it to a String, but is there a better solution?

public byte[] DigitsToAsciiArr(int value) {
  String strValue = String.valueOf(value);
  byte[] byteArray = new byte[strValue.length()];

  for (int i = 0; i < strValue.length(); i++) {
    byteArray[i] = (byte) strValue.charAt(i);
  }
  return byteArray;
}
holvold
  • 43
  • 6
  • 1
    You might want to use a tool like JMH (Java Microbenchmarking Harness) to benchmark code to measure performance – Thiyagu Jul 03 '23 at 12:31
  • 1
    `byteArray = Integer.toString(value).getBytes()` is more direct – g00se Jul 03 '23 at 12:31
  • @g00se What do you mean by "more direct"? Fewer LoC? It's inherently *indirect* because you're creating a String instance for a task that doesn't require one. – Michael Jul 03 '23 at 12:35
  • @Michael I meant less verbose. The code is already creating a string. If any OP is minutely interested in optimizing this kind of thing, he/she *really* shouldn't be using Java in the first place ;) – g00se Jul 03 '23 at 12:39
  • 1
    @g00se And he acknowledges that it's bad. So yes, your solution is minorly less bad than something self-admittedly bad. – Michael Jul 03 '23 at 12:40
  • 2
    @g00se There's a trillion-dollar finance industry built mostly on Java. Just because you don't personally work on applications that have things like this on the hot path doesn't mean that nobody does. – Michael Jul 03 '23 at 12:51

5 Answers5

3

Here's a relatively simple implementation. It doesn't support negative numbers, for simplicity. A version that does is below. Comments are in-line in the code.

public static byte[] asciiDigits(int value) {
    if (value < 0) throw new IllegalArgumentException();

    // Iterate through least-significant digits first
    byte[] reverseOrder = new byte[10]; 
    int digits = 0;
    for (; value > 0; ++digits) {
        int digit = value % 10;
        reverseOrder[digits] = (byte) ('0' + digit);
        value /= 10;
    }

    // Now size down the array to the right size and reverse it
    byte[] bytes = new byte[digits];
    for (int i = 0; i < digits; ++i) {
        bytes[i] = reverseOrder[digits - i - 1];
    }
    return bytes;
}

And this one supports negatives. The basic idea is to convert it to a positive number, do all the same steps, and append the minus sign.

public static byte[] asciiDigits(int value) {
    boolean isNegative = false;
    if (value < 0) {
        // The only value where the absolute value is not representable, so just hardcode the array
        if (value == Integer.MIN_VALUE) {
            return new byte[] { 45, 50, 49, 52, 55, 52, 56, 51, 54, 52, 56 };
        }
        value = -value;
        isNegative = true;
    }

    // Iterate through least-significant digits first.
    // We don't yet know how many digits there may be, but 10 is the most digits an int could have
    byte[] reverseOrderBytes = new byte[10];
    int digits = 0;
    for (; value > 0; ++digits) {
        int digit = value % 10;
        reverseOrderBytes[digits] = (byte) ('0' + digit);
        value /= 10;
    }

    // Now reduce the size of the array to the correct size and reverse it, possibly prepending a minus sign
    int signAdjustment = isNegative ? 1 : 0;
    byte[] bytes = new byte[digits + signAdjustment];
    if (isNegative) {
        bytes[0] = 45; // minus sign
    }
    for (int i = 0; i < digits; ++i) {
        bytes[i + signAdjustment] = reverseOrderBytes[digits - i - 1];
    }
    return bytes;
}

The temporary array seems like the most obvious candidate for removal, so I wrote a version that calculates the number of digits first (a couple of comparisons), then iterates through the least-significant digits first, but writes to the array back-to-front.

The numDigits method was from this answer to another question.

public static byte[] asciiDigits(int value) {
    if (value == Integer.MIN_VALUE) {
        return new byte[] { 45, 50, 49, 52, 55, 52, 56, 51, 54, 52, 56 };
    }

    int numDigits = numDigits(value);
    boolean isNegative = value < 0;
    int signAdjustment = isNegative ? 1 : 0;
    byte[] bytes = new byte[numDigits + signAdjustment];
    if (isNegative) {
        bytes[0] = 45; // minus sign
        value = -value;
    }

    for (int i = 0; i < numDigits; ++i) {
        int digit = value % 10;
        bytes[numDigits + signAdjustment - i - 1] = (byte) ('0' + digit);
        value /= 10;
    }
    return bytes;
}

private static int numDigits(int n) {
    if (n < 0) {
        n = -n;
    }
    if (n < 100000) {
        if (n < 100) {
            if (n < 10) return 1;
            return 2;
        }
        else {
            if (n < 1000) return 3;
            if (n < 10000) return 4;
            return 5;
        }
    }
    else {
        if (n < 10000000) {
            if (n < 1000000) return 6;
            return 7;
        }
        else {
            if (n < 100000000) return 8;
            if (n < 1000000000) return 9;
            return 10;
        }
    }
}

I haven't benchmarked any of these. The third version intuitively seems the best, but it's often hard to predict.

Michael
  • 41,989
  • 11
  • 82
  • 128
1

The main problem here is extracting individual decimal digits from a number that is stored in binary. The easiest way is to convert it into a human-readable form (a String essentially), then iterating over the digits. But that is what you want to avoid

You can also handle each digit one by one by using the modulo operator, which spits out the last digit on each iteration. So you need to reverse the order of the output.

public static byte[] digitsToAsciiArr(int value) {
    Stack<Byte> stack = new Stack<>();
    while(value != 0) {
      //this gets the last digit:
      int digit = value%10;
      //the +48 shifts the digit to its ascii value
      stack.push((byte) (digit+48));
      value /= 10;
    }
    //since the loop above handles the digits from last to first,
    // a stack is used to reverse the order for the output.
    //From here, just convert the stack to a byte[]
    //If you are fine returning Byte[] instead of byte[],
    // this can be shortened to just
    // return stack.toArray(Byte[]::new);
    byte[] ret = new byte[stack.size()];
    int index = 0;
    while(!stack.isEmpty()) {
      ret[index++] = stack.pop();
    }
    return ret;
  }
f1sh
  • 11,489
  • 3
  • 25
  • 51
  • Stack is quite bad for this, and tbh for most things. It's thread-safe when it doesn't need to be. Also requires boxing and unboxing the elements. – Michael Jul 03 '23 at 13:24
  • 1
    Also it's important to note that it doesn't support negative numbers. – Michael Jul 03 '23 at 13:26
  • 1
    @Michael true, reversing the order "manually" avoids that overhead. Upvoted your answer for that reason. – f1sh Jul 03 '23 at 13:26
0

Assuming only digits, non-negative numbers of at most 8 digits:

byte[] digits(int n) {
    byte[] b = new byte[8];
    int i = b.length - 1;
    while (n != 0) {
        b[i] = (byte)('0'| (n % 10));
        --i;
        n /= 10;
    }
    return b;
}

The special cases not dealt with:

  • negative numbers
  • 0 which should yield 48; here 0
  • more than 8 digits

It is optimal as the loop exits on the number of digits.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
0
  • First, return array of '0' if value is 0. Special case reduces required code later.
  • Then determine if value is negative. If so, convert to positive and increase ndigits by by 1 to make room for minus sign.
  • then determine max digits by using the remainder function for change in the original value adjusting nDigits in the process. Max power would be 10_000_000 for eight digits. (found this was faster than using Math.log).
  • then allocate array size based on digits and sign if present.
  • assume negative and add minus sign in first position of array.
  • then get each digit using the remainder operator, convert to a byte and store in reverse order. Minus sign will be overwritten if positive number.
public static byte[] DigitsToAsciiArr(int value) {
    if (value == 0) {
        return new byte[]{'0'};
    }
    int power = 10_000_000; // max power for max digits.
    int nDigits = 8; // max digits.
    
    if (value <= 0) {
        value = -value;
        nDigits++;
    }
    // adjust to allocate array size
    while (value % power == value) {
        power /= 10;
        nDigits--;
    }

    byte[] bytes = new byte[nDigits];
    bytes[0] = '-'; // may be overwritten.

    while (value > 0) {
        bytes[--nDigits] = (byte) (value % 10 + '0');
        value /= 10;
    }

    return bytes;
}

Demo

int[] testData = new int[] {0, -1, 1,185,-185, 12345678, -12345678};
for (int value : testData) {
    byte[] bytes = DigitsToAsciiArr(value);
    System.out.printf("%9d -> %s%n", value, Arrays.toString(bytes));
}

prints

        0 -> [48]
       -1 -> [45, 49]
        1 -> [49]
      185 -> [49, 56, 53]
     -185 -> [45, 49, 56, 53]
 12345678 -> [49, 50, 51, 52, 53, 54, 55, 56]
-12345678 -> [45, 49, 50, 51, 52, 53, 54, 55, 56]


WJS
  • 36,363
  • 4
  • 24
  • 39
-3

By adding "0" to the integer in C/C++, we can convert ASCII.

#include

using namespace std;
int intToAscii(int number) {
 return '0' + number;  
}
   int main() 
{
   int number;

   cout<<"The ASCII of " << 5 <<" is " <<intToAscii(5)<<endl;

   intToAscii(5);

   cout<<"The ASCII of "<< 8 <<" is " <<intToAscii(8)<<endl;

   intToAscii(8);

}
Sanjay Bhalani
  • 2,424
  • 18
  • 44
Rheema
  • 1
  • 2