encoding java double as asn1 Real {sign,mantissa,base,exponent}

时间:2017-10-26 19:38:43

标签: java asn.1

我已经实现了自己的ASN.1框架,我的最后一项是将Java Double编码为ASN.1 Real。第一个链接是我的库,然后链接到ASN.1 Reals上的规范。我在我的encodeDoubleValue中访问正确的尾数时遇到的问题。救命啊!

https://github.com/ZiroZimbarra/ASN1

https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#%5B%7B%22num%22%3A41%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22FitH%22%7D%2C519%5D

我在Reals测试中的代码如下所示:

testRealBytes(new byte[] {9, 5, (byte) 0x80, (byte) 0xFE, (byte) 0x55, (byte) 0x55, (byte) 0x55}, 1398101.25);

这是我的测试实现,我的问题出现在第三行代码的编码中。

    private void testRealBytes(byte[] bytes, Object testObj) throws IOException, InstantiationException, IllegalAccessException {
    Object obj = (Double) ASN1InputStream.decode(bytes);
    assertTrue(obj.equals(testObj));
    byte[] newBytes = ASN1OutputStream.encodeObject(obj);
    assertTrue(Arrays.equals(bytes, newBytes));
}

这是我的encodeValue方法:

    public void encodeDoubleValue(Double doubleObj, ASN1OutputStream derStream) throws IOException {
    if(doubleObj == 0) {return; }
    if(doubleObj == Double.POSITIVE_INFINITY) { derStream.write(0x40); return; }
    if(doubleObj == Double.NEGATIVE_INFINITY) { derStream.write(0x41); return; }
    long bits = Double.doubleToLongBits(doubleObj);
    long mantissa = (bits & 0x000fffffffffffffL);
    int exponent = Integer.valueOf(Long.valueOf(((bits & 0x7FF0000000000000L) >> 52) - 1023).toString());
    int base = 2;
    byte signByte = (byte) (((bits & 0x8000000000000000L) > 1) ? 0x40 : 0x00);
    ASN1OutputStream exponentStream = derStream.newStream();
    new ASN1IntegerType().encodeValue(exponent, exponentStream);
    byte[] exponentBytes = exponentStream.toByteArray();
    switch(exponentBytes.length) {
        case 1: 
            derStream.write(0x80 | (byte) signByte | 0x00);
            break;
        case 2:
            derStream.write(0x80 | (byte) signByte | 0x01);
            break;
        case 3:
            derStream.write(0x80 | (byte) signByte | 0x02);
            break;
        default:
            derStream.write(0x80 | (byte) signByte | 0x03);
            derStream.write(exponentBytes.length);
            break;
    }
    derStream.write(exponentBytes);

    byte[] leftBytes = Long.toUnsignedString(mantissa, 16).getBytes();
    int length = leftBytes.length;
    byte[] mantissaBytes = new byte[length];
    for(int i = 0; i < length; i++) {
        mantissaBytes[i] = leftBytes[length - i - 1];
    }
    for(byte b : mantissaBytes) {
        derStream.write(b);
    }
}

1398101.25的结果是

[9, 15, -128, 20, 48, 48, 48, 48, 48, 48, 48, 52, 53, 53, 53, 53, 53]

而不是它应该是什么

[9, 5, -128, -2, 85, 85, 85]

1 个答案:

答案 0 :(得分:1)

除了使用字符(BER / DER仅用于base = 10),而不是像Kevin所评论的那样,并且向后执行它们完全没有意义,你的错误是:

  • Java(以及大多数流行的CPU)使用的IEEE-then-ISO / IEC浮点方案与大多数其他系统一样,在&#39;尾数左侧有隐含的二进制/小数点&# 39; (更好,有意义),不是BER / DER的权利,而且还有一个隐藏的&#39;该隐含点左侧的1位

  • DER(但不是BER)需要对尾数进行归一化(零除外,它已经是特殊的),因此它的最低有效位为1;对于base = 2,这只需要调整指数,但8或16也需要比例因子

此外,您不需要ASN1OutputStream来编码整数,因为Java的内置BigInteger.toByteArray()已经产生了规范的大端二进制补码,并且您实际上不需要处理大于2个八位字节的指数长度,因为它们永远不会出现在IEEE-ISO / IEC值中。 (如果您想要BigDecimal值或类似值,可能需要它们。)

以下代码生成正确的和期望的值(不包括标记和长度):

    double d = 1398101.25d;
    ByteArrayOutputStream str = new ByteArrayOutputStream();
    // for demo, use real output as needed

    long bits = Double.doubleToRawLongBits(d);
    int signByte = (int)(bits>>(63-6))&(1<<6) | (1<<7);
    // shift to the correct place to start with, and pre-add bit 8
    int exponent = ((int)(bits>>52)&0x7FF)-(1023+52);
    // don't need to box/unbox to do arithmetic
    long mantissa = (bits&0xFFFFFFFFFFFFFL) | (1L<<52);
    // add the hidden bit
    while( (mantissa&1)==0 ){ mantissa>>=1; exponent++; }
    // normalize
    byte[] exptbytes = BigInteger.valueOf(exponent).toByteArray();
    if(exptbytes.length<3) str.write(signByte|(exptbytes.length-1)); 
    else{ str.write(signByte|3); str.write(exptbytes.length); }
    // only the if branch is actually needed 
    str.write (exptbytes);
    str.write (BigInteger.valueOf(mantissa/*>>scale*/).toByteArray());

    System.out.println(Arrays.toString(str.toByteArray()));
    // for demo