我目前正在一个限制性环境中工作,其中唯一允许的类型是:
byte, byte[], short, short[].
我几乎可以肯定我无法导入外部库,因为我正在开发JavaCard,并且已经尝试过这样的事情,但结果并不好。
所以,这里我要管理一个大小为6字节的字节数组,它代表卡的余额(以欧元为单位),最后一个字节是美分,但这现在并不重要。
鉴于我无法访问整数,我不知道如何以我想要的方式添加两个字节。
让我们举个例子:
用户输入(添加)
0x00 0x00 0x00 0x00 0x00 0x57
,对于用户来说,这意味着添加 57 美分。现在假设余额为 0x00 ... 0x26
。
我希望能够创建一个可以修改余额数组(带进位)的方法,添加后,分是83,并表示
0x83
。
我也必须处理减法,但我想我以后可以自己解决这个问题。
我的第一个猜测是屏蔽每个字节中的每个数字,并首先单独工作,但这让我无处可去。
我显然不是要求完整的解决方案,因为我相信我的问题几乎是不可能的,但如果您对如何解决这个问题有任何想法,我将非常感激。
那么如何在 Java Card 上将两个包含二进制编码小数的数组相互添加呢?
编辑 1:常见数组如下所示:
{ 0x00 , 0x00 , 0x01, 0x52, 0x45, 0x52}
并且将代表大端 BCD 编码整数中的 15 254 欧元和 52 美分。
编辑2:嗯,正如我怀疑的那样,我的卡不支持framework.math包,所以我不能使用BCDUtil或BigNumbers,这本来是有用的。
下面的实现将逐字节、逐位地执行 BCD。这使得它能够使用在大多数智能卡处理器上高效的 8 位寄存器。它明确允许正确处理进位,并在溢出时返回进位。
/**
* Adds two values to each other and stores it in the location of the first value.
* The values are represented by big endian, packed BCD encoding with a static size.
* No validation is performed if the arrays do indeed contain packed BCD;
* the result of the calculation is indeterminate if the arrays contain anything other than packed BCD.
* This calculation should be constant time;
* it should only leak information about the values if one of the basic byte calculations leaks timing information.
*
* @param x the first buffer containing the packed BCD
* @param xOff the offset in the first buffer of the packed BCD
* @param y the second buffer containing the packed BCD
* @param yOff the offset in the second buffer of the packed BCD
* @param packedBytes the number of bytes that contain two BCD digits in both buffers
* @return zero or one depending if the full calculation generates a carry, i.e. overflows
* @throws ArrayIndexOutOfBoundsException if a packed BCD value is out of bounds
*/
public static byte addPackedBCD(byte[] x, short xOff, byte[] y, short yOff, short packedBytes) {
// declare temporary variables, we'll handle bytes only
byte xd, yd, zd, z;
// set the initial carry to zero, c will only be 0 or 1
byte c = 0;
// go through the bytes backwards (least significant bytes first)
// as we need to take the carry into account
for (short i = (short) (packedBytes - 1); i >= 0; i--) {
// retrieve the two least significant digits the current byte in the arrays
xd = (byte) (x[xOff + i] & 0b00001111);
yd = (byte) (y[yOff + i] & 0b00001111);
// zd is the addition of the lower two BCD digits plus the carry
zd = (byte) (xd + yd + c);
// c is set to 1 if the final number is larger than 10, otherwise c is set to zero
// i.e. the value is at least 16 or the value is at least 8 + 4 or 8 + 2
c = (byte) (((zd & 0b10000) >> 4)
| (((zd & 0b01000) >> 3)
& (((zd & 0b00100) >> 2) | ((zd & 0b00010) >> 1))));
// subtract 10 if there is a carry and then assign the value to z
z = (byte) (zd - c * 10);
// retrieve the two most significant digits the current byte in the arrays
xd = (byte) ((x[xOff + i] >>> 4) & 0b00001111);
yd = (byte) ((y[yOff + i] >>> 4) & 0b00001111);
// zd is the addition of the higher two BCD digits plus the carry
zd = (byte) (xd + yd + c);
// c is set to 1 if the final number is larger than 10, otherwise c is set to zero
// i.e. the value is at least 16 or the value is at least 8 + 4 or 8 + 2
c = (byte) (((zd & 0b10000) >> 4)
| (((zd & 0b01000) >> 3)
& (((zd & 0b00100) >> 2) | ((zd & 0b00010) >> 1))));
// subtract 10 if there is a carry and then assign the value to the 4 msb digits of z
z |= (zd - c * 10) << 4;
// assign z to the first byte array
x[xOff + i] = z;
}
// finally, return the last carry
return c;
}
请注意,我仅针对包含单个字节/两个 BCD 数字的两个数组进行了测试。然而,进位是有效的,并且由于所有 65536 个组合都已经过测试,因此该方法必定是有效的。
最重要的是,您可能需要在执行任何操作之前测试打包 BCD 编码的正确性。可以将相同的方法集成到加法的
for
循环中,以提高效率。如上一个代码块所示,针对所有单字节值进行了测试。
/**
* Checks if the buffer contains a valid packed BCD representation.
* The values are represented by packed BCD encoding with a static size.
* This calculation should be constant time;
* it should only leak information about the values if one of the basic byte calculations leaks timing information.
*
* @param x the buffer containing the packed BCD
* @param xOff the offset in the buffer of the packed BCD
* @param packedBytes the number of bytes that packed BCD in the buffer
* @return true if and only if the value is valid, packed BCD
* @throws ArrayIndexOutOfBoundsException if the packed BCD value is out of bounds
*/
public static boolean validPackedBCD(byte[] x, short xOff, short packedBytes) {
// declare temporary variable, we'll handle bytes only
byte xdd;
// c is the correctness of the digits; it will be off-zero if invalid encoding is encountered
byte c = 0;
short end = (short) (xOff + packedBytes);
// go through the bytes, reusing xOff for efficiency
for (; xOff < end; xOff++) {
xdd = x[xOff];
// c will be set to non-zero if the high bit of each encoded decimal is set ...
// and either one of the two decimals is set as that would indicate a value of 10 or higher
// i.e. only values 8 + 4 or 8 + 2 are 10 or higher if you look at the bits in the digits
c |= ((xdd & 0b1000_1000) >> 2) & (((xdd & 0b0100_0100) >> 1) | (xdd & 0b0010_0010));
}
// finally, return the result - c is zero in case all bytes encode two packed BCD values
return c == 0;
}
BCDUtil
也实现了这一点。然而,我确实不喜欢这种类设计,而且我认为它没有很好的记录,所以我决定采取不同的策略。它也在 javacardx
中,这意味着如果不实现,理论上它可能会抛出异常。
EJP 的答案不适用,除了表明所使用的编码是打包BCD 的编码之外。 Jones 提出的加法很快,但它没有显示如何处理 32 位字之间的进位:
请注意,如果该位置应该有进位,则总和的最高有效位将超过 9。此外,没有简单的方法来检测这种携带!
这当然是 Java Card 所必需的,因为它只有 16 位有符号短整型和基本类型整数。因此,琼斯提出的方法不能直接适用;任何利用 Jones 方法的答案都应该指出如何处理 Java Card 中使用的字节或 Shorts 之间的进位。
这不是真正的十六进制,它是压缩十进制,BCD 的形式之一。请参阅
Jones 谈 BCD 算术
,其中展示了如何有效地使用 32 位字上的位操作数来实现压缩十进制算术。扩展到 64 位(即 16 个 BCD 数字)是微不足道的。Donald E. Knuth,计算机编程的艺术,
卷。图 4A 的第 7.3.1 节练习 100 给出了加法和减法的算法,这些算法又很容易扩展到 64 位/16 BCD 数字。也可在分册 1 中使用相同的章节编号。