顯示具有 精度 標籤的文章。 顯示所有文章
顯示具有 精度 標籤的文章。 顯示所有文章

2025年4月29日 星期二

[研究]ASP.NET,WebForm,e.NewValues存入 DB 的 float 欄位後可能變了(二)

[研究]ASP.NET,WebForm,e.NewValues存入 DB 的 float 欄位後可能變了(二)

2025-04-29

環境:Visual Studio 2022 + ASP.NET + WebForm + Web Application + C# + SQL Server 2019 + SQL Server Management Studio (SSMS) 19

********************************************************************************

續

[研究]ASP.NET,WebForm,e.NewValues存入 DB 的 float 欄位後可能變了(一)
https://shaurong.blogspot.com/2025/04/aspnetwebformenewvalues-db-float.html

********************************************************************************

擷取發生狀況的程式中的相關部分,寫了程式測試

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default1.aspx.cs"
    Inherits="WebApplication1.Default1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:MyDBConnectionString %>"
            DeleteCommand="DELETE FROM [Table58] WHERE [SN] = @SN"
            InsertCommand="INSERT INTO [Table58] ([floatField], [decimalField], [realField], [numericField]) 
                VALUES (@floatField, @decimalField, @realField, @numericField)"
            SelectCommand="SELECT * FROM [Table58]"
            UpdateCommand="UPDATE [Table58] SET [floatField] = @floatField, [decimalField] = @decimalField, 
                [realField] = @realField, [numericField] = @numericField WHERE [SN] = @SN">
            <DeleteParameters>
                <asp:Parameter Name="SN" Type="Int32" />
            </DeleteParameters>
            <InsertParameters>
                <asp:Parameter Name="floatField" Type="Double" />
                <asp:Parameter Name="decimalField" Type="Decimal" />
                <asp:Parameter Name="realField" Type="Single" />
                <asp:Parameter Name="numericField" Type="Decimal" />
            </InsertParameters>
            <UpdateParameters>
                <asp:Parameter Name="floatField" Type="Double" />
                <asp:Parameter Name="decimalField" Type="Decimal" />
                <asp:Parameter Name="realField" Type="Single" />
                <asp:Parameter Name="numericField" Type="Decimal" />
                <asp:Parameter Name="SN" Type="Int32" />
            </UpdateParameters>
        </asp:SqlDataSource>
        <asp:GridView ID="GridView1" runat="server" AllowPaging="False" AllowSorting="True" AutoGenerateColumns="False"
            EmptyDataText="無資料" DataKeyNames="SN" DataSourceID="SqlDataSource1">
            <Columns>
                <asp:BoundField DataField="SN" HeaderText="SN" InsertVisible="False" ReadOnly="True" SortExpression="SN" />
                <asp:BoundField DataField="floatField" HeaderText="floatField" SortExpression="floatField" />
                <asp:BoundField DataField="decimalField" HeaderText="decimalField" SortExpression="decimalField" />
                <asp:BoundField DataField="realField" HeaderText="realField" SortExpression="realField" />
                <asp:BoundField DataField="numericField" HeaderText="numericField" SortExpression="numericField" />
            </Columns>
        </asp:GridView>
        <br />
        <asp:DetailsView ID="DetailsView1" DefaultMode="Edit" runat="server"
            Height="50px" Width="600px" AutoGenerateRows="False" DataKeyNames="SN" DataSourceID="SqlDataSource1"
            OnItemInserted="DetailsView1_ItemInserted"
            OnItemInserting="DetailsView1_ItemInserting" AllowPaging="True" OnItemUpdating="DetailsView1_ItemUpdating">
            <Fields>
                <asp:BoundField DataField="SN" HeaderText="SN" InsertVisible="False" ReadOnly="True" SortExpression="SN" />
                <asp:TemplateField HeaderText="floatField" SortExpression="floatField">
                    <InsertItemTemplate>
                        <asp:TextBox ID="TextBox_floatField" runat="server" Text='<%# Bind("floatField") %>'></asp:TextBox>
                    </InsertItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="TextBox_floatField" runat="server" Text='<%# Bind("floatField") %>'></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="Label1" runat="server" Text='<%# Bind("floatField") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="decimalField" SortExpression="decimalField">
                    <InsertItemTemplate>
                        <asp:TextBox ID="TextBox_decimalField" runat="server" Text='<%# Bind("decimalField") %>'></asp:TextBox>
                    </InsertItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="TextBox_decimalField" runat="server" Text='<%# Bind("decimalField") %>'></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="Label2" runat="server" Text='<%# Bind("decimalField") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="realField" SortExpression="realField">
                    <InsertItemTemplate>
                        <asp:TextBox ID="TextBox_realField" runat="server" Text='<%# Bind("realField") %>'></asp:TextBox>
                    </InsertItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="TextBox_realField" runat="server" Text='<%# Bind("realField") %>'></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="Label3" runat="server" Text='<%# Bind("realField") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="numericField" SortExpression="numericField">
                    <InsertItemTemplate>
                        <asp:TextBox ID="TextBox_numericField" runat="server" Text='<%# Bind("numericField") %>'></asp:TextBox>
                    </InsertItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="TextBox_numericField" runat="server" Text='<%# Bind("numericField") %>'></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="Label4" runat="server" Text='<%# Bind("numericField") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:CommandField ShowInsertButton="True" ShowDeleteButton="True" ShowEditButton="True" />
            </Fields>
        </asp:DetailsView>
        <asp:Button ID="Button1" runat="server" Text="新增10筆" OnClick="Button1_Click" />
        <asp:Button ID="Button2" runat="server" Text="更新10筆" OnClick="Button2_Click" />
    </form>
</body>
</html>



Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    public partial class Default1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
           
        }

        protected void DetailsView1_ItemInserted(object sender, DetailsViewInsertedEventArgs e)
        {
            GridView1.DataBind();
        }

        protected void DetailsView1_ItemInserting(object sender, DetailsViewInsertEventArgs e)
        {

            string floatField = e.Values["floatField"].ToString().Trim();
            Int32.TryParse(floatField, out int floatFieldInt);
            e.Values["floatField"] = floatFieldInt / 100.0;

            string decimalField = e.Values["decimalField"].ToString().Trim();
            Int32.TryParse(decimalField, out int decimalFieldInt);
            e.Values["decimalField"] = decimalFieldInt / 100.0;

            string realField = e.Values["realField"].ToString().Trim();
            Int32.TryParse(realField, out int realFieldInt);
            e.Values["realField"] = realFieldInt / 100.0;

            string numericField = e.Values["numericField"].ToString().Trim();
            Int32.TryParse(numericField, out int numericFieldInt);
            e.Values["numericField"] = numericFieldInt / 100.0;
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            DetailsView1.ChangeMode(DetailsViewMode.Insert);

            TextBox TextBox_floatField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_floatField = row.FindControl("TextBox_floatField") as TextBox;
                if (TextBox_floatField != null)
                {
                    break; // 找到就跳出
                }
            }

            TextBox TextBox_decimalField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_decimalField = row.FindControl("TextBox_decimalField") as TextBox;
                if (TextBox_decimalField != null)
                {
                    break; // 找到就跳出
                }
            }

            TextBox TextBox_realField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_realField = row.FindControl("TextBox_realField") as TextBox;
                if (TextBox_realField != null)
                {
                    break; // 找到就跳出
                }
            }

            TextBox TextBox_numericField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_numericField = row.FindControl("TextBox_numericField") as TextBox;
                if (TextBox_numericField != null)
                {
                    break; // 找到就跳出
                }
            }

            for (int i = 50; i <= 60; i++)
            {
                TextBox_floatField.Text = i.ToString();
                TextBox_decimalField.Text = i.ToString();
                TextBox_realField.Text = i.ToString();
                TextBox_numericField.Text = i.ToString();
                DetailsView1.ChangeMode(DetailsViewMode.Insert);
                DetailsView1.InsertItem(true);
            }
        }
        protected void Button2_Click(object sender, EventArgs e)
        {
            DetailsView1.ChangeMode(DetailsViewMode.Edit);

            TextBox TextBox_floatField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_floatField = row.FindControl("TextBox_floatField") as TextBox;
                if (TextBox_floatField != null)
                {
                    break; // 找到就跳出
                }
            }

            TextBox TextBox_decimalField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_decimalField = row.FindControl("TextBox_decimalField") as TextBox;
                if (TextBox_decimalField != null)
                {
                    break; // 找到就跳出
                }
            }

            TextBox TextBox_realField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_realField = row.FindControl("TextBox_realField") as TextBox;
                if (TextBox_realField != null)
                {
                    break; // 找到就跳出
                }
            }

            TextBox TextBox_numericField = null;
            foreach (DetailsViewRow row in DetailsView1.Rows)
            {
                TextBox_numericField = row.FindControl("TextBox_numericField") as TextBox;
                if (TextBox_numericField != null)
                {
                    break; // 找到就跳出
                }
            }

            for (int i = 60; i <= 70; i++)
            {
                TextBox_floatField.Text = i.ToString();
                TextBox_decimalField.Text = i.ToString();
                TextBox_realField.Text = i.ToString();
                TextBox_numericField.Text = i.ToString();

                DetailsView1.UpdateItem(true);
            }
        }

        protected void DetailsView1_ItemUpdating(object sender, DetailsViewUpdateEventArgs e)
        {
            string floatField = e.NewValues["floatField"].ToString().Trim();
            Int32.TryParse(floatField, out int floatFieldInt);
            e.NewValues["floatField"] = floatFieldInt / 100.0;

            string decimalField = e.NewValues["decimalField"].ToString().Trim();
            Int32.TryParse(decimalField, out int decimalFieldInt);
            e.NewValues["decimalField"] = decimalFieldInt / 100.0;

            string realField = e.NewValues["realField"].ToString().Trim();
            Int32.TryParse(realField, out int realFieldInt);
            e.NewValues["realField"] = realFieldInt / 100.0;

            string numericField = e.NewValues["numericField"].ToString().Trim();
            Int32.TryParse(numericField, out int numericFieldInt);
            e.NewValues["numericField"] = numericFieldInt / 100.0;
        }
    }
}

SSMS 中刪除資料表內容使用

TRUNCATE TABLE [MyDB].[dbo].[Table58];
DBCC CHECKIDENT ("Table58", RESEED, 1);

下圖,

測試數次,無法重現問題,還有非預期的因素影響了,可能先不管了,要找時間改 DB Schema 和 ASP.NET 程式。

(完)

相關

[研究]ASP.NET,WebForm,e.NewValues存入 DB 的 float 欄位後可能變了(一)

[研究]ASP.NET,WebForm,e.NewValues存入 DB 的 float 欄位後可能變了(一)

2025-04-28

環境:Visual Studio 2022 + ASP.NET + WebForm + Web Application + C# + SQL Server 2019 + SQL Server Management Studio (SSMS) 19

********************************************************************************

最近碰到一個況狀,一個小數值,e.NewValues 存入DB 的 float 欄位後,幾乎都正常,但遇到一個值居然變了的情況;在 Visual Studio 2019 中 Debug,追蹤 DetailsView1_ItemUpdating() 的 e.NewValues 值到最後,都是正常的,但DB更新結果的值卻不符;研究了一下。

********************************************************************************

ChatGPT 說:

這個問題最常見的原因是:

1. SQL Server的 float 型別本身是「近似值」(approximate numeric)

float 在 SQL Server 裡 不是精確數字,它是 IEEE 754 浮點格式,會有小數誤差。例如:

0.58 在 float 中,實際 binary 儲存時可能是類似 0.5799999xxx

然後如果你查詢時 SELECT ROUND(Rate1, 2),看到的是四捨五入後的值

但是如果系統(或者某些 ORM、自訂轉換)有先把 float 小數轉成文字或其他處理,就可能出現 0.57

簡單說:float 不適合存「需要準確小數」的資料,像利率、金額,都應該用 decimal(p,s)。

2. 你在 ASP.NET、ADO.NET 的轉型過程有細節誤差

如果你用的程式碼是這樣類型的:

float value = Convert.ToSingle(e.NewValues["Rate1"]);   

或

command.Parameters.AddWithValue("@Rate1", e.NewValues["Rate1"]);   

因為 e.NewValues 裡面的值其實是 object,很可能已經經過一輪型別轉換(例如從 TextBox 字串變 float),過程中又有誤差。

3. SQL Server 資料型態跟 .NET 送入時的小數精度不一致

float 預設在 SQL Server 會是 float(53),但如果你明確建表時用了 float(24) 這種低精度 float,問題會更嚴重。

float(24) 大概只有 7 位有效數字

float(53) 有 15~16 位有效數字

解決建議 
把 DB 欄位改成 **decimal(10,4)**(或其他適合的 p,s),保證 0.58 進去就是 0.5800

如果不能改 DB 結構,至少在 .NET 端明確用 decimal 處理,不要用 float

例如在後端這樣轉型:

decimal value = Convert.ToDecimal(e.NewValues["Rate1"]);
command.Parameters.Add("@Rate1", SqlDbType.Decimal).Value = value;   

並且資料表欄位是 decimal(10,4)。

********************************************************************************

未何 DB 中欄位定義是 float,但是 ASP.NET WebForm 產出的 <asp:Parameter Name="Rate1" Type="Double" /> 中是 double  ?

ChatGPT 說:

你的問題非常專業,來,我直接重點回答:

1. SQL Server 的 float 對應到 .NET 是 double

在 SQL Server 裡,float 是一種「近似數字」,但它沒有直接對應到 C# 裡的 float (C# 的 float 是 32-bit 喔)。

實際上:

SQL Server 的 float(53) (預設就是 53 位元的 float)→ 對應到 .NET C# 的 double(64-bit 浮點數)

SQL Server 的 real (低精度浮點)→ 才對應到 .NET C# 的 float(32-bit)

也就是說,SQL Server 的 float 預設就是高精度版本,所以 ASP.NET WebForm 在 Parameter 自動產生時,會設定成:

<asp:Parameter Name="Rate1" Type="Double" />

而不是 Type="Single"(Single 才是 .NET 的 float)

這是正確且預期的行為!

2. 微軟官方對應表(簡單版)

SQL Server 資料型態.NET 型態asp:Parameter Type
floatSystem.DoubleDouble
realSystem.SingleSingle
decimal / numericSystem.DecimalDecimal

3. 但是!精度問題還是存在

即使 .NET 這邊是 double,但因為 SQL Server 的 float 是近似值(approximate),小數點附近會有細微誤差,這就是為什麼你會遇到像 0.58 變成 0.57 這種情形。

********************************************************************************

SQL Server 那些資料型別 是「近似值」(approximate numeric)

ChatGPT 說:只有兩種:

資料型別說明
float浮點數,近似值,精度高(通常是 53 位元,對應 .NET 的 double)
real浮點數,近似值,但精度比 float 低(對應 .NET 的 float)

這兩個型別儲存的是「浮點表示」的數字,會有細微誤差,不是精確儲存小數位。

********************************************************************************

.NET Framework 裡,「近似值」(approximate numeric)的資料型別哪幾種

ChatGPT 說:只有兩種:

.NET 型別大小精度說明
float (System.Single)32 位元(4 bytes)大約 7 位十進位有效數字單精度浮點數(single-precision floating point)
double (System.Double)64 位元(8 bytes)大約 15~16 位十進位有效數字雙精度浮點數(double-precision floating point)

這兩種型別都是基於 IEEE 754 標準儲存方式,本質上是近似值,不是完全精確。

********************************************************************************

 .NET Framework 數值型別分類圖

數值型別
├── 精確值 (Exact Numeric)
│   ├── 整數型 (Integer Types)
│   │   ├── byte    (0 ~ 255)
│   │   ├── sbyte   (-128 ~ 127)
│   │   ├── short   (-32,768 ~ 32,767)
│   │   ├── ushort  (0 ~ 65,535)
│   │   ├── int     (-2^31 ~ 2^31-1)
│   │   ├── uint    (0 ~ 2^32-1)
│   │   ├── long    (-2^63 ~ 2^63-1)
│   │   └── ulong   (0 ~ 2^64-1)
│   └── 小數型 (Decimal Type)
│       └── decimal (28~29 位十進位有效數字,適合金額、財務運算)
│
└── 近似值 (Approximate Numeric)
    ├── 浮點型 (Floating Point Types)
    │   ├── float  (System.Single, 32-bit, 約7位有效數字)
    │   └── double (System.Double, 64-bit, 約15~16位有效數字)

********************************************************************************

如果需要儲存精確小數,SQL Server 欄位建議使用哪種資料型別,對應到 .NET Framework 是哪種型別 ?

ChatGPT 說:

SQL Server 儲存「精確小數」的最佳建議:

SQL Server 型別對應 .NET Framework 型別特性
decimal(p, s) 或 numeric(p, s)System.Decimal精確小數,絕對不失真,適合金額、比例、利率等數據

✅ decimal 跟 numeric 在 SQL Server 是完全等價的,只是名字不同,通常大家習慣用 decimal。

SQL Server 型別 vs .NET 型別 完整對應表:

SQL Server 型別.NET 型別說明
decimal(p,s)System.Decimal高精度小數
numeric(p,s)System.Decimal高精度小數(跟 decimal 相同)

(其他型別像 float、real 對應到 double、float,但那是近似,不建議用。)

********************************************************************************

(完)

相關