MySQL 8:我应该能够写入一个有效的 IEEE 754 浮点数并读回准确的值吗?
MySQL 8: Should I be able to write a valid IEEE 754 floating point number and read the exact value back?
下面的程序将一个浮点数写入数据库,然后再读回。
写入的浮点值的十六进制表示为:
a379eb4c
从数据库中读出的值为:
bd79eb4c
写入的值看起来像一个有效的 IEEE 754 浮点值(参见 here).The MySQL docs mention IEEE 754 here 和内部存储的一些注释
格式依赖于机器。该程序使用 MySQL 8.0.16 和 Windows 10 上的 64 位 ODBC 驱动程序,因此我的假设是始终使用 IEEE 754。
程序的完整输出为:
written (hex): a379eb4c
read (hex): bd79eb4c
written (dec): 123456792
read (dec): 123457000
如何解释差异?
我是不是漏掉了什么设置?
程序(C#,Visual Studio 2019,带有 System.Data.Odbc
包的 .Net Core):
using System;
namespace MySqlOdbcFloatTest
{
class Program
{
static void Main(string[] args)
{
var connectionString = "DSN=MySql64";
using (var connection = new System.Data.Odbc.OdbcConnection(connectionString))
{
connection.Open();
// create table
using (var cmd = new System.Data.Odbc.OdbcCommand("create table TestFloatTable (SomeFloat float)", connection))
{
cmd.ExecuteNonQuery();
}
// insert float
float floatToWrite = 123456789.0f;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
cmd.CommandText = "insert TestFloatTable (SomeFloat) values (?)";
var p = new System.Data.Odbc.OdbcParameter();
p.OdbcType = System.Data.Odbc.OdbcType.Real;
p.Value = floatToWrite;
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
}
// read float back
float floatRead;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
cmd.CommandText = "select SomeFloat from TestFloatTable";
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)reader.GetValue(0); // GetValue returns a float object
}
// write hex values
Console.Write("written (hex): ");
var floatWrittenBytes = BitConverter.GetBytes(floatToWrite);
foreach (var b in floatWrittenBytes)
{
Console.Write(string.Format("{0:x2}", b));
}
Console.WriteLine();
Console.Write(" read (hex): ");
var floatReadBytes = BitConverter.GetBytes(floatRead);
foreach (var b in floatReadBytes)
{
Console.Write(string.Format("{0:x2}", b));
}
Console.WriteLine();
// write decimal values
Console.Write("written (dec): ");
Console.WriteLine(floatToWrite.ToString("F0"));
Console.Write(" read (dec): ");
Console.WriteLine(floatRead.ToString("F0"));
}
}
}
}
您正在遇到 MySQL bug 87794。浮点数以全精度存储,但不以全精度返回给客户端。
MySQL uses the FLT_DIG
constant (which equals to 6 with IEEE 754 encoding)
to print float-type numbers. FLT_DIG is the number of decimal digits
that can be converted to a float-type binary number and back without
loss of precision for any input number. That doesn't mean there are no
numbers with more significant digits that can be represented precisely
in the binary format (and your case is an example of such a number), but
the constant ensures that property for all inputs.
您可以使用一些解决方法。
二进制协议
默认情况下,MySQL 使用 "text protocol" 使用 ASCII 数字通过线路发送数字。这是 FLT_DIG
起作用的地方。相比之下,二进制协议发送 32 位 IEEE 754 浮点数。
我不知道 ODBC 连接器是否可以公开二进制协议,但是 MySqlConnector 可以公开准备好的语句。
using (var connection = new MySqlConnection("...;IgnorePrepare=false"))
{
// read float back
float floatRead;
using (var cmd = new MySqlCommand())
{
cmd.Connection = connection;
cmd.CommandText = "select SomeFloat from TestFloatTable";
// ADD THIS LINE
cmd.Prepare();
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)reader.GetValue(0); // GetValue returns a float object
}
}
强制转换为双精度
如果您对结果执行计算,则 MySQL 会将其强制为双精度。这将在网络上正确格式化,您的客户端将读取正确的结果。请注意,对于 MySqlConnector,结果值现在将键入为 double
; ODBC 连接器很可能以相同的方式工作:
// read float back
float floatRead;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
// ADD "+0" TO THE SELECT STATEMENT
cmd.CommandText = "select SomeFloat+0 from TestFloatTable";
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)(double)reader.GetValue(0); // GetValue returns a double object
// ALTERNATIVELY, just use GetFloat
floatRead = reader.GetFloat(0);
}
下面的程序将一个浮点数写入数据库,然后再读回。
写入的浮点值的十六进制表示为:
a379eb4c
从数据库中读出的值为:
bd79eb4c
写入的值看起来像一个有效的 IEEE 754 浮点值(参见 here).The MySQL docs mention IEEE 754 here 和内部存储的一些注释 格式依赖于机器。该程序使用 MySQL 8.0.16 和 Windows 10 上的 64 位 ODBC 驱动程序,因此我的假设是始终使用 IEEE 754。
程序的完整输出为:
written (hex): a379eb4c
read (hex): bd79eb4c
written (dec): 123456792
read (dec): 123457000
如何解释差异?
我是不是漏掉了什么设置?
程序(C#,Visual Studio 2019,带有 System.Data.Odbc
包的 .Net Core):
using System;
namespace MySqlOdbcFloatTest
{
class Program
{
static void Main(string[] args)
{
var connectionString = "DSN=MySql64";
using (var connection = new System.Data.Odbc.OdbcConnection(connectionString))
{
connection.Open();
// create table
using (var cmd = new System.Data.Odbc.OdbcCommand("create table TestFloatTable (SomeFloat float)", connection))
{
cmd.ExecuteNonQuery();
}
// insert float
float floatToWrite = 123456789.0f;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
cmd.CommandText = "insert TestFloatTable (SomeFloat) values (?)";
var p = new System.Data.Odbc.OdbcParameter();
p.OdbcType = System.Data.Odbc.OdbcType.Real;
p.Value = floatToWrite;
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
}
// read float back
float floatRead;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
cmd.CommandText = "select SomeFloat from TestFloatTable";
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)reader.GetValue(0); // GetValue returns a float object
}
// write hex values
Console.Write("written (hex): ");
var floatWrittenBytes = BitConverter.GetBytes(floatToWrite);
foreach (var b in floatWrittenBytes)
{
Console.Write(string.Format("{0:x2}", b));
}
Console.WriteLine();
Console.Write(" read (hex): ");
var floatReadBytes = BitConverter.GetBytes(floatRead);
foreach (var b in floatReadBytes)
{
Console.Write(string.Format("{0:x2}", b));
}
Console.WriteLine();
// write decimal values
Console.Write("written (dec): ");
Console.WriteLine(floatToWrite.ToString("F0"));
Console.Write(" read (dec): ");
Console.WriteLine(floatRead.ToString("F0"));
}
}
}
}
您正在遇到 MySQL bug 87794。浮点数以全精度存储,但不以全精度返回给客户端。
MySQL uses the
FLT_DIG
constant (which equals to 6 with IEEE 754 encoding) to print float-type numbers. FLT_DIG is the number of decimal digits that can be converted to a float-type binary number and back without loss of precision for any input number. That doesn't mean there are no numbers with more significant digits that can be represented precisely in the binary format (and your case is an example of such a number), but the constant ensures that property for all inputs.
您可以使用一些解决方法。
二进制协议
默认情况下,MySQL 使用 "text protocol" 使用 ASCII 数字通过线路发送数字。这是 FLT_DIG
起作用的地方。相比之下,二进制协议发送 32 位 IEEE 754 浮点数。
我不知道 ODBC 连接器是否可以公开二进制协议,但是 MySqlConnector 可以公开准备好的语句。
using (var connection = new MySqlConnection("...;IgnorePrepare=false"))
{
// read float back
float floatRead;
using (var cmd = new MySqlCommand())
{
cmd.Connection = connection;
cmd.CommandText = "select SomeFloat from TestFloatTable";
// ADD THIS LINE
cmd.Prepare();
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)reader.GetValue(0); // GetValue returns a float object
}
}
强制转换为双精度
如果您对结果执行计算,则 MySQL 会将其强制为双精度。这将在网络上正确格式化,您的客户端将读取正确的结果。请注意,对于 MySqlConnector,结果值现在将键入为 double
; ODBC 连接器很可能以相同的方式工作:
// read float back
float floatRead;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
// ADD "+0" TO THE SELECT STATEMENT
cmd.CommandText = "select SomeFloat+0 from TestFloatTable";
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)(double)reader.GetValue(0); // GetValue returns a double object
// ALTERNATIVELY, just use GetFloat
floatRead = reader.GetFloat(0);
}