MySQL 8: Могу ли я написать действительное число с плавающей точкой IEEE 754 и прочитать обратно точное значение? - PullRequest
1 голос
/ 05 февраля 2020

Программа ниже записывает число с плавающей точкой в ​​базу данных и затем считывает его обратно.

Шестнадцатеричное представление записанного значения с плавающей точкой:

a379eb4c

Значение, считываемое из базы данных:

bd79eb4c

Записанное значение выглядит как действительное значение с плавающей точкой IEEE 754 (см. здесь ). MySQL Документы упоминают IEEE 754 здесь с некоторыми замечаниями о том, что формат внутреннего хранилища зависит от машины. Эта программа использует MySQL 8.0.16 с 64-битным драйвером ODB C на Windows 10, и поэтому я предполагаю, что повсеместно используется IEEE 754.

Полный вывод программы:

written (hex): a379eb4c
   read (hex): bd79eb4c
written (dec): 123456792
   read (dec): 123457000

Как объяснить разницу?

Я где-то пропускаю настройку?

Программа (C#, Visual Studio 2019,. Net Core с System.Data.Odbc пакетом):

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"));
            }
        }
    }
}

1 Ответ

1 голос
/ 06 февраля 2020

Вы столкнулись с MySQL ошибкой 87794 . Плавающее число хранится с полной точностью, но не возвращается клиенту с полной точностью.

MySQL использует константу FLT_DIG (равную 6 с кодировкой IEEE 754) для печати числа типа float. FLT_DIG - это число десятичных цифр, которое можно преобразовать в двоичное число с плавающей запятой и обратно без потери точности для любого входного числа. Это не означает, что нет чисел с более значимыми цифрами, которые могут быть точно представлены в двоичном формате (и ваш случай является примером такого числа), но константа обеспечивает это свойство для всех входных данных.

Вы можете использовать несколько обходных путей.

Двоичный протокол

По умолчанию MySQL использует «текстовый протокол», который отправляет числа по проводам с использованием цифр ASCII. Это где FLT_DIG имеет эффект. В отличие от этого, двоичный протокол отправляет 32-разрядные значения IEEE 754.

Я не знаю, может ли соединитель ODB C выставлять двоичный протокол, но 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; вполне возможно, что разъем ODB C работает так же:

// 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);
}
...