-1

I've been experimenting with Java floats, and I've noticed some weird behaviour that doesn't appear to occur in another language, like C#.

// Expecting 'x' to round down to 1500.455.
float x = 1500.45504f;
// Expecting 'y' to round up to 2000.3741.
float y = 2000.37408f;

System.out.println(x + ", " + y);

The above code produces the output 1101.4551, 1106.374. Should the rounding not be consistent for both floats? Why is the 1101.45504 round to .4551, whereas the 1106.37408 value is being essentially truncated to .374? Should it not be the other way around?

Edit: The main intention behind this question was asking why different languages treat floats differently. As Eric has mentioned, Java's Float.ToString() method does append one more digit to distinguish between adjacent floats, but why is this not the case for C#? And how can I represent this behaviour in other languages, such as C#.

Community
  • 1
  • 1
Nova
  • 31
  • 4
  • 1
    How this string is produced is documented [right here](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Float.html#toString(float)). – Sweeper Jul 18 '23 at 09:50
  • 1
    Are you sure anything is "rounded" at all and you are not just specifying a number that float simply cannot represent precisely!? – luk2302 Jul 18 '23 at 09:53
  • 2
    if you print `new BigDecimal(x)`, you'll get `1500.455078125`, while `new BigDecimal(y)` is `2000.3740234375` (BTW I doubt that you got the output `1101.4551, 1106.374` from that code! more like `1500.4551, 2000.374`) – user16320675 Jul 18 '23 at 10:04
  • 1
    The numbers are stored identically, using IEEE 754. Whatever difference there is is entirely limited to the different ways you're printing them in the different languages. Since you haven't shown how you're doing that in C++, we can't comment on it. – Michael Jul 18 '23 at 10:25
  • 3
    Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Jorn Jul 18 '23 at 10:59
  • @luk2302: Re “Are you sure anything is "rounded" at all”: There are four roundings in the code shown, one when each of the two decimal numerals in the source code is rounded to an IEEE-754 format and one when each of those `float` values is rounded to decimal for output. – Eric Postpischil Jul 18 '23 at 11:01

2 Answers2

3

Should the rounding not be consistent for both floats?

The rounding is consistent for both float values. It does not round to a decimal numeral. It rounds to a binary numeral, with 24 significant bits.

In binary, 1500.45504 is 10111011100.01110100011111011000000001011110010111101…. Rounding that to 24 bits produces 10111011100.011101001, which is 1500.455078125.

In binary, 2000.37408 is 11111010000.0101111111000011101101001111011000010110…. Rounding that to 24 bits produces 11111010000.0101111111000, which is 2000.3740234375.

The default formatting used to convert that to a decimal numeral produces just enough decimal digits to uniquely distinguish the float number. For 1500.455078125, the adjacent float values below and above it are 1500.4549560546875 and 1500.4552001953125. If any of those were rounded to seven digits total (three after the decimal point), the result would be “1500.455” for all of them. So one more digit is needed to distinguish 1500.455078125 from the others. So it is rounded to eight decimal digits, producing “1500.4551”.

Similarly, for 2000.3740234375, the adjacent float values are 2000.3739013671875 and 2000.3741455078125, and rounding any of those to seven digits produces “2000.374”. In this case, though, rounding the ones below and above it to eight digits distinguish them, producing “200.3739” and “2000.3741”, so the algorithm allows seven digits for our number, 2000.3740234375, producing “2000.374”, because it knows this uniquely distinguishes it since the others will produce different results.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

Floats in Java and C# use IEEE 754 single precision format, which has a precision of 7 decimal digits (well, a bit above 7). This means that when you define a float variable with more than 7 decimal digits (like 1500.45504f), the value will be rounded to the nearest representable float value. In both languages variable will hold the same binary values (what can be easily verified).

The differences you are seeing are related to how those languages implement by default floating point rounding and precision in their standard libraries. In particular, the differences are in how they round the last digit and in the number of digits they print. You can read on it in Java implementation here: Float.toString(float) and for C#: System.Single.ToString.

You can make the output look exactly the same with some fiddling with format specifiers:

in c#:

float x = 1500.45504f;
float y = 2000.37408f;
Console.WriteLine("{0:G9}, {1:G9}", x, y);

outputs:

1500.45508, 2000.37402

in java:

float x = 1500.45504f;
float y = 2000.37408f;
System.out.printf("%.9g, %.9g%n", x, y);

outputs the same:

1500.45508, 2000.37402

The interesting thing is that in case of Java-s default behaviour, you can convert your floats to string and back to floats and have the exact same values. In case of c# its not true for both of them - to acomplish it - you need a round-trip flag when calling: float_variable.ToString("R").

marcinj
  • 48,511
  • 9
  • 79
  • 100