LLBLGen v5.9.1 Precision Problem in Double Type

Posts   
 
    
bimar
User
Posts: 12
Joined: 17-Oct-2017
# Posted on: 17-May-2022 23:52:34   

Dear LLBLGen Team,

We’re trying to migrate LLBLGen version 5.5.4 to v5.9.1 with Oracle database version 12C and using target framework .NET standard for generate source code.

And I 'am having some trouble with precision value after that set the entity's field.

I have a field named "exchange rate" in the database and its configures as NUMBER(15,9). When the source code is generated, the type of this field is creating as double. There is no problem so far, but when we want to assign a double type value to this field, a different assignment is made from the value we want to assign.

I will try to explain this with an example below unit test;

As you see I create one double variable with value 2.9633, and then I assigned it to my entity’s field named as “OtomatikKur” (this fields database data type is Number(15,9)).

[TestMethod()]
public void FatFaturaKurTest()
 {
  FatFaturaKurEntity fatFaturaKurEntity = new FatFaturaKurEntity();
  double exchangeRate = 2.9633;
  fatFaturaKurEntity.OtomatikKur = exchangeRate;

  Console.WriteLine(exchangeRate);
  Console.WriteLine(fatFaturaKurEntity.OtomatikKur);
 }

When this code runs output looks like this;

exchangeRate value is : 2.9633

fatFaturaKurEntity.OtomatikKur (entity's field) value is : 2.963299999

Two values look different from each other. This problems is very critical for our app and for this issue causes errors in billing accounts made through the application.

Also, we examined why this error might be caused by the ORMSupportClass project and noticed that the code below differs between version 5.5 and 5.9.

In EntityCore classes the codes in which the double type is managed have changed.

This code belongs to v5.5.4

case TypeCode.Double:
if((fieldToValidate.Precision > 0) && (fieldToValidate.Scale > 0))
{
Double valueAsDouble = Math.Abs((Double)value);
valueAsString = valueAsDouble.ToString();
FieldUtilities.CheckPrecisionAndScale(valueAsString, ref value, ScaleOverflowCorrectionActionToUse, fieldToValidate.Precision, fieldToValidate.Scale, out precisionViolation, out scaleViolation, out exceptionMessage);
}
break;

And this is for v5.9.1

case TypeCode.Double:
if(fieldToValidate.Scale > 0)
{
value = FieldUtilities.CheckPrecisionScaleSingleDouble(value, ScaleOverflowCorrectionActionToUse, fieldToValidate.Scale);
}
break;

ScaleOverflowCorrectionActionToUse property uses as "Truncate" on default for 5.5 and 5.9. But this implementation is differentiated for 5.9.1. And we think this where the errors comes from. Actually as implementation the new version should support the old application codes but now it doesn't. Maybe this issue should be considered as breaking change.

Version 5.5.4 (ScaleOverflowCorrectionActionToUse --> truncate implementation)

switch (scaleOverflowCorrectionActionToUse) {
case ScaleOverflowCorrectionAction.None:
  exceptionMessage = string.Format("The scale of value '{0}' is larger than the scale of the field: '{1}' and will cause an overflow in the database. ", value, scale);
  break;
case ScaleOverflowCorrectionAction.Truncate:
case ScaleOverflowCorrectionAction.Round:
  scaleViolation = false;
  value = CorrectScale(value, scale, scaleOverflowCorrectionActionToUse, out absoluteValueAfterCorrection);
  scaleCorrected = true;
  break;
}

Version 5.9.1 (ScaleOverflowCorrectionActionToUse --> truncate implementation)

switch (overflowActionToUse) {
  // there's an overflow in the scale. 
  // we'll get an overflow otherwise, so we'll fall through and truncate. 
case ScaleOverflowCorrectionAction.Round when scale < 16:
  // simply round it. no exception check needed. 
  valueToReturn = Math.Round(value, scale);
  break;
case ScaleOverflowCorrectionAction.None:
  // no action is performed
  valueToReturn = value;
  break;
default:
  double truncatedFraction = fractionMultipliedWithPowerTruncated / scalePower;
  // construct the value to return. Make sure to add the fraction back the right way in the case the original value is negative
  valueToReturn = (value < 0) ? valueWithoutFraction - truncatedFraction : valueWithoutFraction + truncatedFraction;
  break;
}

As a result, we do not want any changes in the fields we set as double on entity fields.

How should we proceed in this point? Is there a way to do this without changing the property's (ScaleOverflowCorrectionActionToUse) value, what do you recommend for this problem?

My CustomerId: 19501

Best regards.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 38963
Joined: 17-Aug-2003
# Posted on: 18-May-2022 09:14:26   

This is sadly a result of the fact a double isn't an exact value but an approximation (IEEE encoded floating point value). This means that 2.933 isn't exactly 2.933 but an approximation of 2.933. See this documentation page for details: https://www.llblgen.com/Documentation/5.9/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/gencode_validation.htm#defining-the-scale-overflow-correction-action-to-use

The scale checks in our runtime were changed because of some edge cases with the original code which gave wrong results and we updated the code so the code would provide better result. This is documented in this thread: https://www.llblgen.com/tinyforum/Thread/27448#150230. This thread also contains a workaround to overcome this issue.

We did consider removing it in v5.9 (as described in the thread above) but in the end decided to keep our code as it worked fine for most cases. What we should have done too is provide better tools for users such as yourself to fine tune the actions to take e.g. whether or not you want scale truncation happening for this particular field or not. We'll try to add these in a future release.

For now, please look at the thread I linked above and check if the workaround discussed there are sufficient for your case. If not please let us know.

Frans Bouma | Lead developer LLBLGen Pro
bimar
User
Posts: 12
Joined: 17-Oct-2017
# Posted on: 18-May-2022 15:15:09   

Hi Otis,

Before opening this topic, we looked at the workarounds in the link you sent, thanks for sharing.

After your message, we made changes in ORMSupportClass based on version 5.9.1 as you can see below. For values of type double, we run the code as in version 5.5.4. And with this change we have now achieved the desired result.

Inside the following method in EntityCore class --> bool ValidateValue(IFieldInfo fieldToValidate, ref object value, int fieldIndex)

Orginal v5.9.1 Code;

  case TypeCode.Single:
  case TypeCode.Double:
     if (fieldToValidate.Scale > 0) {
           value = FieldUtilities.CheckPrecisionScaleSingleDouble(value, ScaleOverflowCorrectionActionToUse, fieldToValidate.Scale);
     }
  break;

We changed it as below (same 5.5.4);

case TypeCode.Single:
  if ((fieldToValidate.Precision > 0) && (fieldToValidate.Scale > 0)) {
    Single valueAsSingle = Math.Abs((Single) value);
    valueAsString = valueAsSingle.ToString();
    FieldUtilities.CheckPrecisionAndScale(valueAsString, ref value, ScaleOverflowCorrectionActionToUse, fieldToValidate.Precision, fieldToValidate.Scale, out precisionViolation, out scaleViolation, out exceptionMessage);
  }
break;
case TypeCode.Double:
  if ((fieldToValidate.Precision > 0) && (fieldToValidate.Scale > 0)) {
    Double valueAsDouble = Math.Abs((Double) value);
    valueAsString = valueAsDouble.ToString();
    FieldUtilities.CheckPrecisionAndScale(valueAsString, ref value, ScaleOverflowCorrectionActionToUse, fieldToValidate.Precision, fieldToValidate.Scale, out precisionViolation, out scaleViolation, out exceptionMessage);
  }
break;

We think this is a workaround similar to the ones in the link you sent, do you see any inconveniences in moving forward with these changes?

Best regards.

Walaa avatar
Walaa
Support Team
Posts: 14824
Joined: 21-Aug-2005
# Posted on: 18-May-2022 20:51:44   

Looks fine to me and that's what was suggested at the end of the shared thread.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 38963
Joined: 17-Aug-2003
# Posted on: 19-May-2022 08:29:29   

Only downside is that if you update the runtime to a later version (e.g. a bugfix version) you have to migrate this change too. If that's ok, sure go ahead, otherwise we need to add an option which disables the truncation (as it's actually useless) for float/double fields (off by default but if you enable it, it'll leave the values as-is, and leaves it up to ODP.NET's rounding code)

Frans Bouma | Lead developer LLBLGen Pro
bimar
User
Posts: 12
Joined: 17-Oct-2017
# Posted on: 20-May-2022 01:37:31   

Only downside is that if you update the runtime to a later version (e.g. a bugfix version) you have to migrate this change too

As you mentioned above, we realize that we need to consider this issue when changing runtime for later version. For now we decided to continue with the method I mentioned in my previous message.

It would also be very useful to have an option like the one below in later versions.

we need to add an option which disables the truncation (as it's actually useless) for float/double fields

Thanks for your supports @Otis and @Walaa