【问题标题】:Make an binary addition behave like a (packed-) decimal addition使二进制加法的行为类似于(压缩)十进制加法
【发布时间】:2018-01-31 05:43:37
【问题描述】:

我目前正在一个限制性环境中工作,其中允许的唯一类型是:

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,这会很有用。

【问题讨论】:

  • 您的第一个猜测是正确的。最好的方法是分别处理每个字节中的每个数字。
  • 你的最大值和最小值是多少?
  • 那些高 5 字节是否代表某种 BCD 中的欧元(而不是美分),或者这只是一个“正常”的 40 位整数?
  • 想加的时候,26 - 57

标签: java javacard bcd


【解决方案1】:

下面的实现逐字节和逐位地遍历 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;
}

请注意,这也是在 Java Card 中的BCDUtil 中实现的。但是,我确实不喜欢该类设计,并且我认为它的文档记录不充分,因此我决定采用不同的方法。它也在javacardx 中,这意味着如果不实现,理论上它可能会抛出异常。


answer of EJP isn't applicable,除了表示使用的编码是压缩 BCD 的编码。 Jones 提出的加法速度很快,但没有说明如何处理 32 位字之间的进位:

请注意,如果该位置应该有进位,则总和的最高有效位将超过 9。此外,没有简单的方法来检测这种携带!

这对于 Java Card 来说当然是必需的,因为它只有 16 位有符号短整数基类型。因此,琼斯提出的方法不能直接适用;任何利用 Jones 方法的答案都应该指出如何处理 Java Card 中使用的字节或短字节之间的进位

【讨论】:

  • 这不是 Jones 提供的方式,而且效率要低得多。您的 Javadoc 也是错误的:这里没有 BigIntegers。我不知道你为什么发现琼斯页面没有帮助。我在很久以前使用与 Jones 随后发布的代码非常相似的代码将所有这些东西实现到 COBOL 编译器中。
  • 1.没有人对任何事情大喊大叫。 2. 你没有任何证据证明谁对你的答案投了反对票。不要做出无根据的断言。
  • 无论你在这里要说什么,你在评论中的反应都是不好;您基本上是在说我的回答毫无价值,因为您作为超级程序员可以做得更好。但是,好吧,让我们保持技术性。您是否同意琼斯解决方案不能直接适用没有办法处理携带?如果您能以某种方式使用字节和短裤创建性能更好的解决方案,我很乐意支持它。
【解决方案2】:

这不是真正的十六进制,它是压缩十进制,是 BCD 的一种形式。

您可以使用内部进位一次执行压缩十进制加法和减法一个字节。如有必要,有一个技巧是加 6 以强制进位进入 MS 位,然后在进位时将其屏蔽并再次移出,以纠正 LS 位。这里解释的太笼统了。

请参阅Jones on BCD arithmetic,它展示了如何有效地使用 32 位字上的位操作数来实现压缩十进制算法。

【讨论】:

  • 我习惯称它为压缩 BCD 而不是压缩十进制,但我认为两者都是正确的。顺便说一句,未打包的 BCD 只存储每个字节的一个十进制数字;打包 BCD 每个半字节存储一个数字,由 4 位组成,每个字节存储两个数字。
  • @MaartenBodewes 是的,这就是为什么它被称为压缩十进制。还有分区十进制,它使用“区域”位进行解包,使其可打印,或者换句话说,使用 0x30-0x39 ('0'-'9') 而不是 0x00-0x09。
  • 添加了关于 Jones 方法的解释并被否决,因为它不直接适用,请参阅我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-12
  • 1970-01-01
  • 1970-01-01
  • 2021-02-26
  • 1970-01-01
  • 2012-05-27
  • 2016-08-01
相关资源
最近更新 更多