Java:IEEE双向IBM Float

问题描述 投票:-2回答:1

我正在做一个工作的侧面项目,我想读/写SAS Transport files。挑战在于数字是用64位IBM floating point numbers编码的。虽然我已经能够找到很多很好的资源来读取一个字节数组(包含一个IBM浮点数)到IEEE 32位浮点数和64位浮点数,但我很难找到将浮点数/双精度数转换回代码的代码。 IBM浮动。

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

我还尝试将SAS Transport文档末尾列出的函数转换为Java,但是我遇到了很多与endiness相关的问题,Java缺少无符号类型等等。任何人都可以提供将双精度转换为IBM浮点格式的代码吗?

为了展示我所取得的进展,以下是我目前编写的代码的一些缩短版本:

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

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浮动,但现在我需要走另一条路。这个简单的实现似乎适用于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浮点数一起使用。列出的许多幻数与IEEE 32位浮点指数(8位)和尾数(23位)中的位数有关。所以对于64位,我只需要切换那些使用11位指数和52位尾数。但126来自哪里? 0x3循环中的while有什么意义?

任何帮助打破32位版本,所以我可以实现64位版本将不胜感激。

java ieee-754
1个回答
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[]和原语/字符串之间来回加载;然而,这带来了一些小的开销。但它会处理任何字节序问题。事实证明,endianness甚至不是一个值得关注的问题,因为今天使用的大多数架构(x86)都是开始时的小端。

看起来读/写SAS传输文件是一个非常普遍的需求,特别是在临床试验领域,所以希望任何使用Java / C#的人都不必经历我所做的麻烦。

© www.soinside.com 2019 - 2024. All rights reserved.