PDA

View Full Version : Bug in property editors for string to float conversions



Paul Newport
Oct 26th, 2006, 06:16 AM
I have a major problem in an application, in that if I have a float on an object, and am mapping between a jsp field (i.e. a String) and a float, I am getting conversion errors if the screen field is 9 digits or larger.

Example:

I enter 333333333, navigate to another page, come back, it shows the field I entered as 333333344.

An easy to run test case demonstrates this (see end of post).

I have debugged the Spring code and what is happening is that in org.springframework.util.NumberUtils it is executing this piece of code:


else if (targetClass.equals(Float.class)) {
return new Float(number.floatValue());
}


I can see that number is a Long, with a value of 333333333. However number.floatValue() returns 333333344 which is completely wrong.

The bottom line is that Spring MVC doesn't seem to work properly when mapping jsp fields to floats and back again.

Can someone double check my test and see if I am doing anything wrong ?




import java.text.DecimalFormat;
import java.text.NumberFormat;

import junit.framework.TestCase;

import org.springframework.beans.propertyeditors.CustomNu mberEditor;

public class TestCorruptFloat extends TestCase {

NumberFormat numberFormat;
String expected = "333333333";

public void setUp() {
// number format editor
numberFormat = new DecimalFormat("0.00");
numberFormat.setMinimumFractionDigits(2);

}

public void testConvert() {

CustomNumberEditor customNumberEditor = new CustomNumberEditor(
Float.class, numberFormat, true);


customNumberEditor.setAsText(expected);

assertTrue(((Float)(customNumberEditor.getValue()) ).floatValue()==333333333);
String actual = customNumberEditor.getAsText();
System.out.println(expected + " " + actual);
assertEquals(expected, actual);

}


}

Andreas Senft
Oct 26th, 2006, 07:05 AM
As you already tracked down the issue to NumberUtils class there could be seen that this is no issue of Spring. Rather this is a limitation of representable floating point numbers in Java.

Just try this:


System.out.println(new Float("333333333"));

and you will see the same result.

Use Double or, better, BigDecimal, instead when you have to handle large numbers.

Regards,
Andreas

Paul Newport
Oct 26th, 2006, 08:42 AM
As you already tracked down the issue to NumberUtils class there could be seen that this is no issue of Spring. Rather this is a limitation of representable floating point numbers in Java.

Just try this:


System.out.println(new Float("333333333"));

and you will see the same result.

Use Double or, better, BigDecimal, instead when you have to handle large numbers.

Regards,
Andreas

I don't doubt what you are saying but I am a bit confused.

Try this:

Float big = new Float("333333333");
System.out.println(big.floatValue()/333333333);

and you will see that it prints out 1.0, so

new Float("333333333").floatValue()

must be 333333333 regardless of what the toString() method is displaying.

This is why this line of my test is working, as floatValue is working correctly but toString is not.

assertTrue(((Float)(customNumberEditor.getValue()) ).floatValue()==333333333);

At this point the custom number editor has the correct float value in it.

It is actually the line

return this.numberFormat.format(value);

in method getAsText() in class org.springframework.beans.propertyeditors.CustomNu mberEditor that is returning the String incorrectly as 333333344.

I have tried double and this fixes everything, but there are two issues really:

a) changing all my floats to doubles is a BIG change in the application
b) how come

Float big = new Float("333333333");
System.out.println(big.floatValue()/333333333);

prints out 1.0, rather than whatever 333333344/333333333 is ?

Andreas Senft
Oct 26th, 2006, 09:22 AM
Float big = new Float("333333333");
System.out.println(big.floatValue()/333333333);
and you will see that it prints out 1.0, so
new Float("333333333").floatValue()
must be 333333333 regardless of what the toString() method is displaying.

This is why this line of my test is working, as floatValue is working correctly but toString is not.

Seems plausible to me. Though I found another interesting case:


Float big = new Float("333333333");
System.out.println(big.longValue());

yields 333333344

So not only toString() behaves weird. Anyway it seems to me that the problem is located more at the Java side of things...

Regards,
Andreas

Paul Newport
Oct 26th, 2006, 09:35 AM
Seems plausible to me. Though I found another interesting case:


Float big = new Float("333333333");
System.out.println(big.longValue());

yields 333333344

So not only toString() behaves weird. Anyway it seems to me that the problem is located more at the Java side of things...

Regards,
Andreas

Yes I spotted that too, but luckily this works

Double dubya = new Double("333333333");
System.out.println(dubya);

Thanks for your help. Moral of the story is do not use float, and to a lesser extent double, unless you are Pentium of the Borg who likes being approximated.

Andreas Senft
Oct 26th, 2006, 09:40 AM
Moral of the story is do not use float, and to a lesser extent double, unless you are Pentium of the Borg who likes being approximated.

:cool:

I hope your further proceedings here are not futile. ;)

Regards,
Andreas

Jörg Heinicke
Oct 26th, 2006, 10:03 AM
I wonder why you find this "weird" behaviour that surprising?! It's floating point, so it's not exact. And the same will happen with Double as well, it is only a bit more exact - but not exact in the absolute sense. So just use BigDecimal which is of arbitrary precision.

Jörg

Andreas Senft
Oct 26th, 2006, 10:13 AM
I do not find the lack of precision weird (I already mentioned that fact in my first post). I find it weird, that the division with a long value seems to be correct in spite of the fact that the long representation "seems" to be wrong.

Regards,
Andreas