0

I've been banging my head against the wall all day trying to figure why this is happening and how to fix it. I am building a C# .NET MVC web application. I call a query using Linq from the Database like this:

calibHistory = db.CalibrationHistory
                 .Where(d => d.ID == EquipmentId && d.ChangeNum == maxChangeNum).First();

And get the C# double type (equivalent to a float in SQL) with the following precision:

2.3980000019073486

I take this object and bind it to a form in a partial view. This works and is fine. However, something happens in the Razor rendering that causes the following value to be rounded off to this:

2.39800000190735

Obviously, Razor is rounding off the last two digits (the last two digits up the 4 to a 5). Debugging shows that Razor knows the correct value - in the model where the double is held, you see the double with full precision. Here's where things get interesting, however: a call in SQL Server Management Server shows that the Razor value has the correct precision, not the double returned by Linq.

So somehow, Linq is getting more precision than what is being provided by SQL Server. I want to be able to compare the posted values from the form against the Linq query above to see if there are changes. I haven't been able to figure this out so any help would be greatly appreciated.

TL;DR: Linq is returning more precision for doubles and messing up equality measures. Would like to either fix the Linq query or make Razor show the full precision.

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
dxh9845
  • 23
  • 4
  • > "Linq is returning more precision for doubles and messing up equality measures" . What are your equality measures for `double` comparison ? – Tigran Jun 13 '17 at 18:30
  • 1
    I'd suggest to read this: [Floating Point Demystified, Part 1](http://blog.reverberate.org/2014/09/what-every-computer-programmer-should.html) – Maciej Los Jun 13 '17 at 18:31
  • I am using a custom equals function for my POCO model that compares two doubles and one int. Because the Linq query has more precision, the double comparison against the posted value and the Linq value will always equate to false which is unwanted behavior – dxh9845 Jun 13 '17 at 18:34
  • There is no way LINQ (or ADO.NET) to return more precision than the database. Most likely SSMS UI is truncating it when displaying it as text (as any UI). Try `2.3980000019073486.ToString()` and you'll get the same output as Razor and SSMS. – Ivan Stoev Jun 13 '17 at 18:34
  • I should mention I tried a Display annotation on my POCO class to display these doubles **as full precision doubles** but it had no effect – dxh9845 Jun 13 '17 at 18:37
  • Checking IEEE-754 conversion, it seems like both values have a pure representation, so the issue is probably the Razor display. Can you post the actual code you are using to display the value? – NetMage Jun 13 '17 at 18:40
  • 1
    Basically you need the [The Round-trip ("R") Format Specifier](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#RFormatString), not sure if you can apply it in Razor view. – Ivan Stoev Jun 13 '17 at 18:40
  • You should never compare floating point values on equality, but rather on an acceptable epsilon difference. – juharr Jun 13 '17 at 18:41
  • @IvanStoev I added an internal MetaData class ala this [link](https://stackoverflow.com/questions/16736494/add-data-annotations-to-a-class-generated-by-entity-framework) but that had no effect on Razor's display). – dxh9845 Jun 13 '17 at 18:43
  • @juharr I need to detect **any** change that is made to this column. This is a history table that my app will only insert into if the posted value is different than the latest value in the table – dxh9845 Jun 13 '17 at 18:44
  • Sounds to me like SQL Server Management Server could easily be using the same rounding that Razor is... – Jon Skeet Jun 13 '17 at 18:44
  • @JonSkeet: of course -- SSMS is a .NET application. (SQL Server is not, however, and indeed it does not treat floating-point values in exactly the same way as does .NET -- but that's another matter and not directly relevant to this question.) – Jeroen Mostert Jun 13 '17 at 18:48
  • Compare `SELECT 2.3980000019073486E0` in SSMS (it will format the value as `2.39800000190735`) vs. `SELECT CONVERT(NVARCHAR(50), 2.3980000019073486E0, 3)` (letting SQL Server do the conversion, result `2.3980000019073486e+000`). This is a display & parsing issue, not a precision issue as such. – Jeroen Mostert Jun 13 '17 at 18:52
  • To everyone who mentioned the R format specifier: yes that was it. I had done that previously but was missing a specific boolean to enable that in the MVC EditorFor. See the answer below – dxh9845 Jun 13 '17 at 19:02

2 Answers2

1

OK thanks to everyone who answered - surprised how many people were monitoring these tags!

It seems like I had the answer all along but didn't fully execute it. I had already worked with the R format specifier, but had not deployed the right combination of variables in my data annotation.

Due to the nature of EntityFramework, I needed to override certain aspects of the generated POCO object. This led me to this solution:

[MetadataType(typeof(CalibrationHistory.MetaData))]
    public partial class CalibrationHistory
    {
        internal class MetaData {
            [Display(Name = "New Sensor Calibration value")]
            [DisplayFormat(DataFormatString ="{0:R}", ApplyFormatInEditMode = true)]
            public double Sensor_Calib;
            [Display(Name = "New Cylinder to Wall Distance value")]
            [DisplayFormat(DataFormatString = "{0:R}", ApplyFormatInEditMode = true)]
            public double Cylinder_to_Wall_Distance;
        }

        public override bool Equals(object obj)
        {
            CalibrationHistory castedObject = (CalibrationHistory)obj;
            if (this.ID == castedObject.ID && this.Cylinder_to_Wall_Distance == castedObject.Cylinder_to_Wall_Distance
                && this.Sensor_Calib == castedObject.Sensor_Calib)
            {
                return true;
            }
            else {
                return false;
            }
        }

In the data annotations, I had only specified a DataFormatString without the ApplyFormatInEditMode. Thus, the changes were not reflected in the textbox and therefore not posting with the needed precision.

Thank you everyone who answered.

dxh9845
  • 23
  • 4
0

Razor itself does not manipulate numbers. If it's rendering a number, it's using the default .ToString() method unless you specify otherwise. You might try adding an explicit .ToString() in your partial view, with a format string, wherever this value is rendering out.

As far as comparing the value when it posts again, you're going to need some code that's a bit more sophisticated than "a=b" because the double data type gets wonky when comparing very small numbers. See here.

It is very likely that your number, 2.3980000019073486, isn't the exact value stored in the database because double is only precise to about 15-16 digits. Consider changing the database's type to decimal, and accordingly, your code, if you need more precision. Decimal is accurate to 28-29 digits.

Xavier J
  • 4,326
  • 1
  • 14
  • 25
  • 1
    Actually that number has a precise IEEE-754 representation, as does ...735. It would be nice to mention using `.ToString("g20")` or better still from @IvanStoev using `.ToString("R")`. – NetMage Jun 13 '17 at 18:42
  • "the `double` data type gets wonky when comparing very small numbers" betrays a common misunderstanding of how floating-point *really* works. How big or small your numbers are isn't immediately relevant, what's relevant is that it's often difficult or impractical to know what the exact value of a floating-point number is, so you simply can't do an exact comparison. That's often an issue, but is not the issue in this question. – Jeroen Mostert Jun 13 '17 at 19:15
  • @JeroenMostert it's a bona fide issue in examples like this question, where the number gets rendered out (often with a fixed number of digits), parsed again, and compared --unsuccessfully -- to the older underlying value stored. I don't presume that everyone will understand the inner workings, but anyone can understand the idea that the program's reading the values on either side of the compare operator as not-equals. – Xavier J Jun 13 '17 at 19:19