Проблема преобразования очень маленьких двойников в плавающие SQL - PullRequest
2 голосов
/ 23 апреля 2009

Я пытаюсь сохранить C # double в MS SQL 2005 в виде числа с плавающей запятой. Тем не менее, кажется, существует ряд небольших чисел, которые являются действительными двойными числами, но которые не являются действительными числами с плавающей точкой. Например, когда я пытаюсь сохранить значение 1e-320, я получаю сообщение об ошибке «указанное значение не является допустимым экземпляром типа float».

Это согласуется с документацией для плавающих SQL, которые имеют наименьшее значение 2,23e-308 http://msdn.microsoft.com/en-us/library/ms173773.aspx

и с документацией для C # double, которые имеют наименьшее значение 5.0e-324 http://msdn.microsoft.com/en-us/library/678hzkk9(VS.71).aspx

Так что мой вопрос в том, как лучше всего справиться с этим - могу ли я преобразовать double как нечто, допустимое в качестве SQL-плавающего числа?

Ответы [ 5 ]

3 голосов
/ 23 апреля 2009

Параметры:

  • Сохраните его как строку и положитесь на c #, чтобы проверить / понять его
  • Хранить мантиссу и экспоненту отдельно: 1 и -320 и собирать вне базы данных

Что вы не можете сделать:

  • Изменение на десятичное не будет работать, потому что тогда у вас будет максимум 38 знаков после запятой

Edit:

SQL Sever просто не понимает этого числа: его нельзя сохранить в SQL Server как число, независимо от того, какая клиентская библиотека или тип данных c # используется, или хитрость.

2 голосов
/ 24 апреля 2009

Если вы хотите сохранить полное значение double, вам, вероятно, потребуется использовать одно из решений @ gbn .

Если вы просто предпочитаете использовать диапазон с плавающей запятой SQL и округлять до нуля, если он находится за пределами этого диапазона, будет довольно тривиально создать вспомогательный метод, который округляет до нуля, если значение находится за пределами диапазона для SQL float.

private static SqlParameter CreateDoubleParameter(string parameterName, double value)
{
    const double SqlFloatEpsilon = 2.23e-308;
    SqlParameter parameter = new SqlParameter(parameterName, SqlDbType.Float);
    parameter.Value = value > -SqlFloatEpsilon && value < SqlFloatEpsilon ? 0d : value;
    return parameter;
}
0 голосов
/ 23 апреля 2009

По сути, попытка сохранить 64-битное значение в 32-битном значении приведет к потере точности, и компилятор правильно предупредит вас о проблеме. Вы действительно должны спросить: «Мне действительно нужна вся эта точность?»

Если вам не нужна точность, то "float output = Convert.ToSingle (inputAsDouble);" сделает свое дело - он просто округлит до ближайшего представимого значения одинарной точности.

Если вам нужна точность, но все же нужно, чтобы значение соответствовало 32-битным значениям, тогда вам нужно как-то ограничить диапазон. Например, если вы знаете, что ваше значение всегда будет в диапазоне от -1e-319 до 1e-319, то вы можете использовать математику с фиксированной запятой для преобразования между сохраненным 32-разрядным значением и двойным значением, которое необходимо использовать для расчеты. Возвращаемое таким образом двойное значение не сможет представлять все возможные числовые значения в вашем диапазоне, но вы будете иметь 32-разрядную гранулярность в этом ограниченном диапазоне, что на самом деле вполне приличная точность.

Например, вы можете создать вспомогательный класс, например:

struct FixedDouble
{
    int storage;

    public FixedDouble(double input)
    {
        storage = DoubleToStorage(input);
    }

    public double AsDouble
    {
        get
        {
            return StorageToDouble(storage);
        }
    }

    const double RANGE = 1e-319;

    public static int DoubleToStorage(double input)
    {
        Debug.Assert(input <= RANGE);
        Debug.Assert(input >= -RANGE);

        double rescaledValue = (input / RANGE) * int.MaxValue;

        return (int)rescaledValue;
    }

    public static double StorageToDouble(int input)
    {
        double rescaledValue = ((double)input / (double)int.MaxValue) * RANGE;

        return rescaledValue;
    }
}

Этот код, вероятно, не будет работать как есть, потому что я только что быстро его выбил, но идея есть: в основном вы жертвуете полным диапазоном, который предлагает вам double, и вместо этого выбираете фиксированную гранулярность между двумя числами а 32-разрядное значение позволяет вам определить точку на числовой линии между этими двумя числами.

0 голосов
/ 23 апреля 2009

Вы можете использовать тип SqlDouble . Это из пространства имен System.Data.SqlTypes , которое

предоставляет классы для собственных типов данных в SQL Server. Эти классы предоставляют более безопасную и быструю альтернативу типам данных, предоставляемым общеязыковой средой исполнения .NET Framework (CLR). Использование классов в этом пространстве имен помогает предотвратить ошибки преобразования типов, вызванные потерей точности. Поскольку другие типы данных преобразуются в и из SqlTypes за кулисами, явное создание и использование объектов в этом пространстве имен также приводит к более быстрому коду.

РЕДАКТИРОВАТЬ: По-видимому, SqlDouble имеет диапазон от -1,79E +308 до 1,79E +308, который совпадает с двойным на стороне положительного экспоненты и не принимает отрицательный расхождение показателя в учете. Похоже, это никогда не будет полезным с точки зрения проверки диапазона. В качестве примечания, другие типы в этом пространстве имен, такие как SqlDateTime, выглядят так, как будто они могут быть полезны для проверки диапазона, но не SqlDouble.

0 голосов
/ 23 апреля 2009

Можно либо изменить тип данных на decimal, либо добавить второй столбец для хранения количества десятичных разрядов, на которое должно быть смещено сохраненное значение, чтобы получить исходное значение. То есть сохранение 0,000456 с использованием этой схемы приведет к сохранению 4,56 и 5, тогда как 34567,89 можно представить как 3,456789 и -4 (минус означает сдвиг влево).

Однако второй вариант может привести к постепенной потере точности.

...