1

This is probably a basic question for out more experienced programmers out there. I'm a bit of a noob and can't work this one out. I'm trying to unpack a binary file and the doco is not too clear on how floats are stored. I have found a routine that does this, but it will only work if I pass an integer array of the bytes. The correct answer is -1865.0. I need to be able to pass the byte array and get the correct answer. How do I need to change the code to make float4byte return -1865.0. Thanks in advance.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class HelloWorld {
    public static void main(String[] args) {
        byte[] bytes = {(byte) 0xC3,(byte) 0X74,(byte) 0X90,(byte) 0X00 };
        int[] ints = {(int) 0xC3,(int) 0X74,(int) 0X90,(int) 0X00 };

        // This give the wrong answer
        float f = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
        System.out.println("VAL ByteBuffer BI: " + f);

        // This give the wrong answer
        f = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getFloat();
        System.out.println("VAL ByteBuffer LI: " + f);

        //This gives the RIGHT answer
        f = float4int (ints[0], ints[1], ints[2], ints[3]);
        System.out.println("VAL Integer : " + f);

        // This gives the wrong answer
        f = float4byte (bytes[0], bytes[1], bytes[2], bytes[3]);
        System.out.println("VAL Bytes : " + f);

    }
    private static float float4int(int a, int b, int c, int d)
    {

        int sgn, mant, exp;
        System.out.println ("IN Int: "+String.format("%02X ", a)+
                String.format("%02X ", b)+String.format("%02X ", c)+String.format("%02X ", d));

        mant = b << 16 | c << 8 | d;
        if (mant == 0) return 0.0f;

        sgn = -(((a & 128) >> 6) - 1);
        exp = (a & 127) - 64;

        return (float) (sgn * Math.pow(16.0, exp - 6) * mant);
    }

    private static float float4byte(byte a, byte b, byte c, byte d)
    {

        int sgn, mant, exp;
        System.out.println ("IN Byte : "+String.format("%02X ", a)+
                String.format("%02X ", b)+String.format("%02X ", c)+String.format("%02X ", d));

        mant = b << 16 | c << 8 | d;
        if (mant == 0) return 0.0f;

        sgn = -(((a & 128) >> 6) - 1);
        exp = (a & 127) - 64;

        return (float) (sgn * Math.pow(16.0, exp - 6) * mant);
    }

}

3 Answers 3

2

The reason why your solution with ByteBuffer doesn't work: the bytes do not match the (Java) internal representation of the float value.

The Java representation is

System.out.println(Integer.toHexString(Float.floatToIntBits(-1865.0f)));

which gives c4e92000

Sign up to request clarification or add additional context in comments.

1 Comment

+1, though strictly speaking, it's not necessarily true that that's "the (Java) internal representation". Float.floatToIntBits is specified to use the IEEE 754 single-precision (binary32) representation, whereas Java implementations are only required to use the same value set for their float-s (not necessarily the same representation of each value).
1

bytes are signed in Java. When calculating the mantissa mant, the bytes are implicitly converted from bytes to ints - with the sign "extended", i.e. (byte)0x90 (decimal -112) gets converted 0xFFFFFF90 (32 bits int). However what you want is just the original bytes' 8 bits (0x00000090).

In order to compensate for the effect of sign extension, it suffices to change one line:

mant = (b & 0xFF) << 16 | (c & 0xFF) << 8 | (d & 0xFF)

Here, in (c & 0xFF), the 1-bits caused by sign extension are stripped after (implicit) conversion to int.


Edit:

The repacking of floats could be done via the IEEE 754 representation which can be obtained by Float.floatToIntBits (which avoids using slow logarithms). Some complexity in the code is caused by the change of base from 2 to 16:

private static byte[] byte4float(float f) {
    assert !Float.isNaN(f);
    // see also JavaDoc of Float.intBitsToFloat(int)
    int bits = Float.floatToIntBits(f);
    int s = (bits >> 31) == 0 ? 1 : -1;
    int e = (bits >> 23) & 0xFF;
    int m = (e == 0) ? (bits & 0x7FFFFF) << 1 : (bits&  0x7FFFFF) | 0x800000;

    int exp = (e - 150) / 4 + 6;
    int mant;
    int mantissaShift = (e - 150) % 4;  // compensate for base 16
    if (mantissaShift >= 0) mant = m << mantissaShift;
    else { mant = m << (mantissaShift + 4); exp--;  }
    if (mant > 0xFFFFFFF) { mant >>= 4; exp++; }  // loose of precision
    byte a = (byte) ((1 - s) << 6 | (exp + 64));
    return new byte[]{ a, (byte) (mant >> 16), (byte) (mant >> 8), (byte) mant };
}

The code does not take into account any rules that may exist for the packaging, e.g. for representing zero or normalization of the mantissa. But it might serve as a starting point.

3 Comments

This solution worked perfectly. Problem solved. Thanks. I am going to need to reverse the process and repack floats in the same way. Not sure I know how. Any tips on how to do that would also be appreciated.
@Andrew L: Added an example implementation for the reverse function byte4float. See edit.
Thanks @halfbit. What I have discovered is that I am working with the old IBM 370 mainframe float. See IBM_Floating_Point_Architecture. This solution did not quite work, but something to work on.
0

Thanks to @halfbit and a bit of testing and minor changes, this routine appears convert IEEE 754 float into IBM float.

public static byte[] byte4float(float f) {
    assert !Float.isNaN(f);
    // see also JavaDoc of Float.intBitsToFloat(int)

    int bits = Float.floatToIntBits(f);
    int s = (bits >> 31) == 0 ? 1 : -1;
    int e = (bits >> 23) & 0xFF;
    int m = (e == 0) ? (bits & 0x7FFFFF) << 1 : (bits&  0x7FFFFF) | 0x800000;

    int exp = (e - 150) / 4 + 6;
    int mant;
    int mantissaShift = (e - 150) % 4;  // compensate for base 16
    if (mantissaShift >= 0) mant = m >> mantissaShift;
    else mant = m >> (Math.abs(mantissaShift));
    if (mant > 0xFFFFFFF) { mant >>= 4; exp++; }  // loose of precision */
    byte a = (byte) ((1 - s) << 6 | (exp + 64));
    return new byte[]{ a, (byte) (mant >> 16), (byte) (mant >> 8), (byte) mant };
}

I think this is right and appears to be working.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.