javafloating-pointinteger

How should I convert a float to an unsigned integer?


I need to convert floats to unsigned integers for a project I'm working on — similar to the basic float-to-int cast, but with an unsigned int as output, returning zero for floats that are negative. I know there's a way to convert them to signed integers, but I need unsigned integers. I can't simply upscale to longs, since I'll also need to convert doubles to unsigned longs. The only way I've found is to convert to a string, then to an unsigned int:

private static NumberFormat genIntFormat() {
    NumberFormat numberFormat = NumberFormat.getIntegerInstance(Locale.ENGLISH);
    numberFormat.setGroupingUsed(false);
    return numberFormat;
}
public static final NumberFormat INT_FORMAT = genIntFormat();

public static int toUnsignedInt(float f) {
    int i;
    try {
        i = Integer.parseUnsignedInt(INT_FORMAT.format(Math.floor(f)));
    } catch (NumberFormatException e) {
        i = 0;
    }
    return i;
}

However, this seems a bit too patchwork for me. I'm sure there's a better way of doing this, but I can't seem to find anything online, and ChatGPT just spouts nonsense like it always does. Is there a better way to convert, and what is it? "Better" in my case means faster.

Edit:


Solution

  • A simple if statement

    I am converting the comments by @Anonymous and the code they link to to this answer. If your float is >= 0, cast to long first, and if within the unsigned int range, cast further to int. Otherwise the result is 0.

    public static int convertFloatToUnsignedInt(float f) {
        if (f >= 0) {
            long asLong = (long) f;
            // Within range of unsigned int?
            if (asLong < 0x1_0000_0000L) {
                return (int) asLong;
            }
        }
        return 0;
    }
    

    Let’s try it with your example float values:

        float[] exampleFloats = { 0.0f, -0.1f, -1.5f, Float.NaN, 1.7f, 3e+9f, 1e+12f };
        for (float exampleFloat : exampleFloats) {
            int asInt = convertFloatToUnsignedInt(exampleFloat);
            System.out.format(Locale.ENGLISH, "%14.1f -> %10s or %11d%n",
                    exampleFloat, Integer.toUnsignedString(asInt), asInt);
        }
    

    Output is

               0.0 ->          0 or           0
              -0.1 ->          0 or           0
              -1.5 ->          0 or           0
               NaN ->          0 or           0
               1.7 ->          1 or           1
      3000000000.0 -> 3000000000 or -1294967296
    999999995904.0 ->          0 or           0
    

    The output shows the float value, the unsigned int value converted to and the signed value of the same int. We see that a float is not able to represent the value 1e+12f precisely and prints as 999999995904.0.

    See the code run online

    Repeating the link by @Anonymous the code is running online here.

    double to unsigned long

    … but what about double to long?

    For the case of converting double to unsigned long using the same criteria, you may convert via BigDecimal. Using the same idea as above:

    private static final BigInteger MAX_UNSIGNED_LONG_PLUS_ONE_AS_BIG_INTEGER
            = BigInteger.ONE.shiftLeft(64);
    private static final BigDecimal MAX_UNSIGNED_LONG_PLUS_ONE
            = new BigDecimal(MAX_UNSIGNED_LONG_PLUS_ONE_AS_BIG_INTEGER);
    
    public static long convertDoubleToUnsignedLong(double d) {
        if (d >= 0) {
            BigDecimal asBigDecimal = new BigDecimal(d);
            // Within unsigned long range?
            if (asBigDecimal.compareTo(MAX_UNSIGNED_LONG_PLUS_ONE) < 0) {
                return asBigDecimal.longValue();
            }
        }
        return 0;
    }
    

    (I wasn’t sure whether the code by @Anonymous handles edge cases around the upper limit of the range correctly, so I wrote the above code instead.)

    Trying this out too:

        double[] exampleDoubles = { 0.0, -1.5, Double.NaN, 1e+19, 1e+20 };
        for (double exampleDouble : exampleDoubles) {
            long asLong = convertDoubleToUnsignedLong(exampleDouble);
            System.out.format(Locale.ENGLISH, "%23.1f -> %20s or %20d%n",
                    exampleDouble, Long.toUnsignedString(asLong), asLong);
        }
    

    Output:

                        0.0 ->                    0 or                    0
                       -1.5 ->                    0 or                    0
                        NaN ->                    0 or                    0
     10000000000000000000.0 -> 10000000000000000000 or -8446744073709551616
    100000000000000000000.0 ->                    0 or                    0
    

    Your own code does not seem to work

    I tried your code too. It does not seem to be correct. In the case of 2.2f taking the floor() yields 2.0, which you convert to float again and then to a String. The String is 2.0 (not just 2). Because of the decimal point Integer.parseUnsignedInt() cannot parse the string and always throws the NumberFormatException. So I believe you will get 0 always.