将整数的二进制表示转换为ASCII码在Java Card中

3
我希望将以二进制格式表示的任意长度整数转换为ASCII格式。例如,对于整数33023,十六进制字节为0x80ff。我想将0x80ff表示为33023的ASCII格式,其十六进制表示为0x3333303233
由于我正在使用不识别String类型的Java Card环境,因此我必须通过二进制操作手动进行转换。
在16位智能卡上的Java Card环境非常受限,您认为最有效的解决方案是什么?

如果您没有String,那么您是否至少有char数组和/或byte数组? - Kevin Anderson
基本原语类型只有 byte、byte[]、int、int[]、short 和 short[]。 - thotheolh
太好了!在紧急情况下,short[]String的一个很好的替代品;即使是byte[]也可以用于ASCII。只要我们在short(或byte)上也有/(除法)和(余数)运算符,我们就万事大吉(;->)。 - Kevin Anderson
可以在 short 类型上同时使用 / 和 %。 - thotheolh
intint[]通常不支持Java Card平台。在指定数字格式时,您只是展示了一个例子:0x80FF。这可能是一个由两个字节0x800xFF组成的无符号大端格式。您正在完全指定输出格式,请确保提供输入格式的说明。 - Maarten Bodewes
1个回答

3

这比你想象的更加棘手,因为它需要进行基数转换,并且基数转换是在整个数字上执行的,使用大整数算术。

当然,这并不意味着我们不能为此创建一种有效的大整数算术实现。这里提供了一个实现,它在左侧填充零(在Java Card上通常需要),并且不使用额外的内存(!)。但如果你想保存原始的大端数值,则可能需要复制它——输入值将被覆盖。将其放在RAM中是十分推荐的。

该代码只需将字节除以新的基数(10表示十进制),返回余数。余数就是下一个最低位数字。由于输入值现在已经被除以,所以下一个余数是比之前的数字位更重要的数字。它不断地除以并返回余数,直到值为零且计算完成。

算法的棘手部分是内部循环,它在原地将值除以10,同时使用尾部划分通过字节返回余数。它每次运行都提供一个余数 / 十进制数字。这也意味着该函数的顺序为O(n),其中n是结果中的数字位数(将尾部划分定义为单个操作)。请注意,n可以通过ceil(bigNumBytes * log_10(256))计算得出:其结果也在预先计算的BCD_SIZE_PER_BYTES表中出现。当然,log_10(256)是一个常数十进制值,大约高达2.408

以下是最终的代码优化(请参见编辑以获取不同版本):

/**
 * Converts an unsigned big endian value within the buffer to the same value
 * stored using ASCII digits. The ASCII digits may be zero padded, depending
 * on the value within the buffer.
 * <p>
 * <strong>Warning:</strong> this method zeros the value in the buffer that
 * contains the original number. It is strongly recommended that the input
 * value is in fast transient memory as it will be overwritten multiple
 * times - until it is all zero.
 * </p>
 * <p>
 * <strong>Warning:</strong> this method fails if not enough bytes are
 * available in the output BCD buffer while destroying the input buffer.
 * </p>
 * <p>
 * <strong>Warning:</strong> the big endian number can only occupy 16 bytes
 * or less for this implementation.
 * </p>
 * 
 * @param uBigBuf
 *            the buffer containing the unsigned big endian number
 * @param uBigOff
 *            the offset of the unsigned big endian number in the buffer
 * @param uBigLen
 *            the length of the unsigned big endian number in the buffer
 * @param decBuf
 *            the buffer that is to receive the BCD encoded number
 * @param decOff
 *            the offset in the buffer to receive the BCD encoded number
 * @return decLen, the length in the buffer of the received BCD encoded
 *         number
 */
public static short toDecimalASCII(byte[] uBigBuf, short uBigOff,
        short uBigLen, byte[] decBuf, short decOff) {

    // variables required to perform long division by 10 over bytes
    // possible optimization: reuse remainder for dividend (yuk!)
    short dividend, division, remainder;

    // calculate stuff outside of loop
    final short uBigEnd = (short) (uBigOff + uBigLen);
    final short decDigits = BYTES_TO_DECIMAL_SIZE[uBigLen];

    // --- basically perform division by 10 in a loop, storing the remainder

    // traverse from right (least significant) to the left for the decimals
    for (short decIndex = (short) (decOff + decDigits - 1); decIndex >= decOff; decIndex--) {

        // --- the following code performs tail division by 10 over bytes

        // clear remainder at the start of the division
        remainder = 0;

        // traverse from left (most significant) to the right for the input
        for (short uBigIndex = uBigOff; uBigIndex < uBigEnd; uBigIndex++) {

            // get rest of previous result times 256 (bytes are base 256)
            // ... and add next positive byte value
            // optimization: doing shift by 8 positions instead of mul.
            dividend = (short) ((remainder << 8) + (uBigBuf[uBigIndex] & 0xFF));

            // do the division
            division = (short) (dividend / 10);

            // optimization: perform the modular calculation using
            // ... subtraction and multiplication
            // ... instead of calculating the remainder directly
            remainder = (short) (dividend - division * 10);

            // store the result in place for the next iteration
            uBigBuf[uBigIndex] = (byte) division;
        }
        // the remainder is what we were after
        // add '0' value to create ASCII digits
        decBuf[decIndex] = (byte) (remainder + '0');
    }

    return decDigits;
}

/*
 * pre-calculated array storing the number of decimal digits for big endian
 * encoded number with len bytes: ceil(len * log_10(256))
 */
private static final byte[] BYTES_TO_DECIMAL_SIZE = { 0, 3, 5, 8, 10, 13,
        15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39 };

为了扩展输入尺寸,只需在表中计算并存储下一个十进制尺寸即可...


1
它在实际的Java Card中运行 :) 。非常感谢您的帮助。 - thotheolh
不用谢。添加了带注释的优化版本。删除了中间版本...再次提醒:请在JC上进行测试! - Maarten Bodewes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接