Java:IEEE倍增至IBM Float

时间:2019-03-13 22:51:23

标签: java ieee-754

我正在工作的一个副项目上,我想读/写SAS Transport files。挑战在于数字以64位IBM floating point numbers编码。虽然我已经找到了很多不错的资源,可以将字节数组(包含IBM浮点数)读入IEEE 32位浮点数和64位浮点数,但我仍在努力寻找将浮点数/双精度数转换回的代码。 IBM上市。

我最近发现some code用于将32位IEEE浮点写回一个字节数组(包含IBM浮点)。它似乎正在工作,所以我一直在尝试将其转换为64位版本。我已经逆向工程了大多数魔术数字的来源,但是现在我已经迷迷了一个多星期。

我还尝试将SAS Transport文档末尾列出的功能转换为Java,但是我遇到了许多与持久性,Java缺少无符号类型等有关的问题。谁能提供将double转换为IBM浮点格式的代码?

只是为了展示我的进步,下面是到目前为止我编写的代码的一些简化版本:

这将从字节数组中获取32位IBM浮点数并生成IEEE浮点数:

public static double fromIBMFloat(byte[] data, int offset) {
    int temp = readIntFromBuffer(data, offset);
    int mantissa = temp & 0x00FFFFFF;
    int exponent = ((temp >> 24) & 0x7F) - 64;
    boolean isNegative = (temp & 0x80000000) != 0;
    double result = mantissa * Math.pow(2, 4 * exponent - 24);
    if (isNegative) {
        result = -result;
    }
    return result;
}

对于64位来说,这是同一件事:

public static double fromIBMDouble(byte[] data, int offset) {
    long temp = readLongFromBuffer(data, offset);
    long mantissa = temp & 0x00FFFFFFFFFFFFFFL;
    long exponent = ((temp >> 56) & 0x7F) - 64;
    boolean isNegative = (temp & 0x8000000000000000L) != 0;
    double result = mantissa * Math.pow(2, 4 * exponent - 24);
    if (isNegative) {
        result = -result;
    }
    return result;
}

太好了!这些适用于IEEE floats,但是现在我需要另辟way径。这个简单的实现似乎适用于32位浮点数:

public static void toIBMFloat(double value, byte[] xport, int offset) {
    if (value == 0.0 || Double.isNaN(value) || Double.isInfinite(value)) {
        writeIntToBuffer(xport, offset, 0);
        return;
    }
    int fconv = Float.floatToIntBits((float)value);
    int fmant = (fconv & 0x007FFFFF) | 0x00800000;
    int temp = (fconv & 0x7F800000) >> 23;
    int t = (temp & 0xFF) - 126;
    while ((t & 0x3) != 0) {
        ++t;
        fmant >>= 1;
    }
    fconv = (fconv & 0x80000000) | (((t >> 2) + 64) << 24) | fmant;
    writeIntToBuffer(xport, offset, fconv);
}

现在,剩下的唯一事情就是将其转换为与64位IBM float一起使用。列出的许多幻数与IEEE 32位浮点指数(8位)和尾数(23位)中的位数有关。因此,对于64位,我只需要切换它们以使用11位指数和52位尾数即可。但是126是从哪里来的呢? 0x3循环中while的意义是什么?

对于帮助分解32位版本以便可以实现64位版本的任何帮助,将不胜感激。

1 个答案:

答案 0 :(得分:0)

我转回去,又对SAS传输文档末尾提供的C实现进行了进一步的调整。事实证明,问题不在我的实现中。这是我的测试问题。

TL; DR 这些是我的64位实现:

public static void writeIBMDouble(double value, byte[] data, int offset) {
    long ieee8 = Double.doubleToLongBits(value);
    long ieee1 = (ieee8 >>> 32) & 0xFFFFFFFFL;
    long ieee2 = ieee8 & 0xFFFFFFFFL;
    writeLong(0L, data, offset);
    long xport1 = ieee1 & 0x000FFFFFL;
    long xport2 = ieee2;
    int ieee_exp = 0;
    if (xport2 != 0 || ieee1 != 0) {
        ieee_exp = (int)(((ieee1 >>> 16) & 0x7FF0) >>> 4) - 1023;
        int shift = ieee_exp & 0x3;
        xport1 |= 0x00100000L;
        if (shift != 0) {
            xport1 <<= shift;
            xport1 |= ((byte)(((ieee2 >>> 24) & 0xE0) >>> (5 + (3 - shift))));
            xport2 <<= shift;
        }
        xport1 |= (((ieee_exp >>> 2) + 65) | ((ieee1 >>> 24) & 0x80)) << 24;
    }
    if (-260 <= ieee_exp && ieee_exp <= 248) {
        long temp = ((xport1 & 0xFFFFFFFFL) << 32) | (xport2 & 0xFFFFFFFFL);
        writeLong(temp, data, offset);
        return;
    }
    writeLong(0xFFFFFFFFFFFFFFFFL, data, offset);
    if (ieee_exp > 248) {
        data[offset] = 0x7F;
    }
}

public static void writeLong(long value, byte[] buffer, int offset) {
    buffer[offset] = (byte)(value >>> 56);
    buffer[offset + 1] = (byte)(value >>> 48);
    buffer[offset + 2] = (byte)(value >>> 40);
    buffer[offset + 3] = (byte)(value >>> 32);
    buffer[offset + 4] = (byte)(value >>> 24);
    buffer[offset + 5] = (byte)(value >>> 16);
    buffer[offset + 6] = (byte)(value >>> 8);
    buffer[offset + 7] = (byte)value;
}

并且:

public static double readIBMDouble(byte[] data, int offset) {
    long temp = readLong(data, offset);
    long ieee = 0L;
    long xport1 = temp >>> 32;
    long xport2 = temp & 0x00000000FFFFFFFFL;
    long ieee1 = xport1 & 0x00ffffff;
    long ieee2 = xport2;
    if (ieee2 == 0L && xport1 == 0L) {
        return Double.longBitsToDouble(ieee);
    }
    int shift = 0;
    int nib = (int)xport1;
    if ((nib & 0x00800000) != 0) {
        shift = 3;
    } else if ((nib & 0x00400000) != 0) {
        shift = 2;
    } else if ((nib & 0x00200000) != 0) {
        shift = 1;
    }
    if (shift != 0) {
        ieee1 >>>= shift;
        ieee2 = (xport2 >>> shift) | ((xport1 & 0x00000007) << (29 + (3 - shift)));
    }
    ieee1 &= 0xffefffff;
    ieee1 |= (((((long)(data[offset] & 0x7f) - 65) << 2) + shift + 1023) << 20) | (xport1 & 0x80000000);
    ieee = ieee1 << 32 | ieee2;
    return Double.longBitsToDouble(ieee);
}

public static long readLong(byte[] buffer, int offset) {
    long result = unsignedByteToLong(buffer[offset]) << 56;
    result |= unsignedByteToLong(buffer[offset + 1]) << 48;
    result |= unsignedByteToLong(buffer[offset + 2]) << 40;
    result |= unsignedByteToLong(buffer[offset + 3]) << 32;
    result |= unsignedByteToLong(buffer[offset + 4]) << 24;
    result |= unsignedByteToLong(buffer[offset + 5]) << 16;
    result |= unsignedByteToLong(buffer[offset + 6]) << 8;
    result |= unsignedByteToLong(buffer[offset + 7]);
    return result;
}

private static long unsignedByteToLong(byte value) {
    return (long)value & 0xFF;
}

这些基本上是文档中内容的一对一翻译,除了我将byte[]转换为一个很长的前期内容,而只是做位纠缠而不是直接使用字节。

我还意识到文档中的代码包含一些特殊情况,这些情况是“遗漏”值的,这些值特定于SAS传输标准,与IBM十六进制浮点数无关。实际上,Double.longBitsToDouble方法检测到无效的位序列,并将其值设置为NaN。我将这段代码移出了,因为它仍然无法正常工作。

好处是,作为本练习的一部分,我确实学习了很多技巧来进行Java中的位操作。例如,通过使用>>>运算符而不是>>运算符可以解决我遇到的涉及符号的许多问题。除此之外,您只需要小心地向上投射以用0xFF0xFFFF等掩盖,以确保忽略该符号。

我还了解了ByteBuffer,它可以促进在byte[]和基元/字符串之间来回加载;但是,这会带来一些小的开销。但是它将处理任何字节序问题。事实证明,字节序甚至都不是问题,因为当今使用的大多数架构(x86)都不是字节序开始的。

读取/写入SAS传输文件似乎是非常普遍的需求,尤其是在临床试验领域,因此希望使用Java / C#的任何人都不必经历我的麻烦。

相关问题