-
Notifications
You must be signed in to change notification settings - Fork 811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WW-4871 Fixes StringConverter and improves it's tests #173
Conversation
format.setMinimumFractionDigits(1); | ||
if (BigDecimal.class.isInstance(value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use { }
even if it is one line.
Tests are failing. |
Thanks! both fixed :) |
I think we are good ... @aleksandr-m what's your opinion? |
format.setMaximumFractionDigits(Integer.MAX_VALUE); | ||
} | ||
else if (Double.class.isInstance(value)) { | ||
format.setMaximumFractionDigits(15); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where 15
comes from? Shouldn't it be 16
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch! I mislead max fraction digits with max precision while they're different. thank you. fixed.
As a quick fix it looks ok. In the future it would be good to make this configurable somehow (defaults, exact digits, settings from locale). |
I would start with this and then add more options when requested |
Tests for convert back also improved within |
format.setMaximumFractionDigits(Integer.MAX_VALUE); | ||
} | ||
else if (Double.class.isInstance(value)) { | ||
format.setMaximumFractionDigits(325); //double MIN_VALUE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yasserzamani Can you explain where these numbers coming from? How do you find them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For BigDecimal theoretically there is no limit for count of fraction digits [1] but technically setMaximumFractionDigits cannot get bigger than Integer.MAX_VALUE.
For Double and Float, I converted Double.MIN_VALUE
and Float.MIN_VALUE
to a not scientific notated string by java 7 8 and 9 and found that numbers.
[1] http://download.oracle.com/javase/6/docs/api/java/math/BigDecimal.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aleksandr-m what do you think about keeping max integer for all instead:
if (BigDecimal.class.isInstance(value) ||
Double.class.isInstance(value) || Float.class.isInstance(value)) {
format.setMinimumFractionDigits(1);
format.setMaximumFractionDigits(Integer.MAX_VALUE);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Fixed
Maybe we shouldn't format numbers at all. Just replace |
You mean calling |
Something like this: if (Number.class.isInstance(value)) {
String str = value.toString();
DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(locale);
return str.replace('.', otherSymbols.getDecimalSeparator());
} else {
return Objects.toString(value, null);
} |
@lukaszlenart Yes. The |
Not sure what you mean by that, here we are converting a number to a String, while in the NumberConverter we are converting a String to a number. |
What point to convert number to string according to locale if it is cannot be converted back (e.g. on form submit). |
@aleksandr-m , it was to fix WW-3171. And I think locale has not any problem to converting back from string, according to this PR's I think it's ok and just needs to expand max fraction digits which this PR does. |
All the converters are And as Yasser pointed out this is all about fraction digits ... what I like in your suggestion about using the |
Łukasz, if you like to not add a |
The |
How previous version detects when needed? I saw previous |
I meant check the tests :) The previous version was using |
Łukasz, I reviewed tests. It seems the root cause is a very old 13 years old issue, WW-455. I think not adding I think we can fix |
This how it was before: // XW-409: If the input is a Number we should format it to a string using the choosen locale and use java's numberformatter
if (Number.class.isAssignableFrom(toType)) {
NumberFormat numFormat = NumberFormat.getInstance(getLocale(context));
if (isIntegerType(toType)) {
numFormat.setParseIntegerOnly(true);
}
numFormat.setGroupingUsed(true);
numFormat.setMaximumFractionDigits(99); // to be sure we include all digits after decimal seperator, otherwise some of the fractions can be chopped
String number = numFormat.format(value);
if (number != null) {
return number;
}
}
return null; // no number ( |
I have no idea why I did use the |
ouch! you are right, I have focused on This makes me wonder why our tests didn't catch this. |
@aleksandr-m , this is a known java floating point behavior because these types use a binary mantissa, they cannot precisely represent many finite decimal numbers, such as 0.1, because these numbers have repeating binary representations. So, IMHO, I don't see any bad thing to display When precise computation is necessary, such as when performing currency calculations, user should not use floating-point types. Instead, an alternative representation that can completely represent the necessary values should be used, e.g. BigDecimal. Are you sure |
@yasserzamani Don't think about computation, think about presentation layer.
No. If you change formatter settings it will be
Why not? It is simple to string. |
Thanks; I saw Maybe we can |
Łukasz, I found some time to go deeper and saw that this happens when value cannot be represented exactly with a finite binary number. Failing test examples are added at end. After all, contrary to my expectation, Following tests fails: public void testDoubleToStringConversionPL() throws Exception {
// given
StringConverter converter = new StringConverter();
Map<String, Object> context = new HashMap<>();
context.put(ActionContext.LOCALE, new Locale("pl", "PL"));
// when cannot be represented exactly with a finite binary number
value = converter.convertValue(context, null, null, null, 0.1 + 0.1 + 0.1, null);
// then produce the shortest decimal representation that can unambiguously identify the true value of the floating-point number
assertEquals("0,3", value);
}
public void testFloatToStringConversionPL() throws Exception {
// given
StringConverter converter = new StringConverter();
Map<String, Object> context = new HashMap<>();
context.put(ActionContext.LOCALE, new Locale("pl", "PL"));
// when cannot be represented exactly with a finite binary number
value = converter.convertValue(context, null, null, null, 0.1f, null);
// then produce the shortest decimal representation that can unambiguously identify the true value of the floating-point number
assertEquals("0,1", value);
} |
I think @aleksandr-m is right and the
user would also be surprised if this wouldn't fail. |
You're right; |
The point is, if you print the final number into the console from the action and later show it in the JSP both should be the same (except decimal separator because we take numbers according the locale). If you're doing some computation like |
@aleksandr-m , @lukaszlenart , while decimal wdyt? It seems we have the issue only with float data type. |
Eureka! I reviewed } else if (number instanceof Number) {
return format(((Number)number).doubleValue(), toAppendTo, pos);
}
|
@yasserzamani Looks like a hack. ;) Why not to use code provided by Lukasz #173 (comment) ? |
Yes I see :) please let me leave it as is until I discuss this issue with jdk team. Łukasz code is ok but please let me keep this hack until jdk's fix as we do not have this issue with other |
I don't like fixing JDK's bugs inside the framework, it's hard to track if the bug was fixed and we supposed to drop the workaround or even make it deprecated and allow users to switch to a new solution. If you decide keep the current solution, please add That being said, I would like to have this fixed and start preparing a new release. |
Yes I see, I just was in wait for a reference jdk url to being added to the TODO (I reported at http://bugreport.java.com/ and they wrote they will list this in their database or contact me for more info if was needed). However, I added the TODO; I'll add a reference url from jdk when was ready :) thanks! |
format.setMaximumFractionDigits(Integer.MAX_VALUE); | ||
// TODO: delete this if statement when jdk fixed java.text.NumberFormat.format's behavior with Float | ||
if (Float.class.isInstance(value)) { | ||
value = Double.valueOf(value.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have just one doubt: re-assigning to an input parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:) you're right! I added a test in a commit which confirms that StringConverter does not change input parameter value
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that this isn't a problem from compiler point-of-view as parameters are always local (compiler makes a copy of them), it's rather a bad style :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh :) I thought you are in doubt because we pass the value parameter by type Object
.
Thanks for learning this to me! I researched and it seems there are two best practices ; using a defined local variable, or returning from method at that line. what do you recommend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use a dedicated local variable in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I think I fixed it in my another commit ; I'm sorry for these a lot of commits :/
@aleksandr-m I'm down with these changes and this PR can be merged IMO |
@lukaszlenart Better than current state. +1 for merging. |
"a double value is able to hold this value precisely", they said after seven days in a comment and simply closed the issue 😮 The issue is visible on bugs.java.com at the following url JDK-8190928. I replied back that if you believe a double value is able to hold 0.1d precisely, then please try: System.out.println(new java.math.BigDecimal(0.1d));
System.out.println(0.1d + 0.1d + 0.1d); The output is:
IT IS NOT:
so a double value also is not able to hold 0.1d precisely, i think. I hope they will reply. I know they're advanced experts but I cannot understand what is the wrong with my understanding :( I hope they will help. |
The linke explains everything ... but you must have at least PhD to understand that fully ;-) |
yeah 😃 however all my current below evidences show this should be a bug:
|
LGTM 👍 |
The most discussed PR ever! |
yeah! I should thank Aleksandr and you for your time to review. In the end it became very better than my beginnings commits. |
As a workaround, you can do to restore to original output: ie 1.0 rather than just 1 format.double={0,number,##0.0} Cheers Greg |
No description provided.