Ошибка точности FP при преобразовании XML -> таблица и ее обработка - PullRequest
2 голосов
/ 24 декабря 2010

В SQL Server 2008 у меня есть значение «0,01» в атрибуте XML. Используя OPENXML, я уничтожил XML во временную таблицу. Если применимый столбец имеет тип real (одинарная точность), в таблице он равен 0,01. Хорошо. Однако, если точность равна float (двойная точность), она получается как 0,00999999977648258. Это не имеет никакого смысла. Почему он это делает?

Мой следующий вопрос: независимо от того, как значение представлено во временной таблице, когда я запускаю на нем статистическую функцию, оно всегда возвращается как 0,00999999977648258. Это приводит к ошибкам проверки: процедура сообщает, что ввод слишком мал (<0,01), что не соответствует действительности. </p>

Есть идеи, почему возникают ошибки округления и как их преодолеть?

Уже пробовал: сделать колонку варчаром.

EDIT2:

Основываясь на ответах, я понимаю, что проблема заключается в том, что числа IEEE не могут точно представлять 0,01. Поэтому мой следующий вопрос:

"ГДЕ {вычислено} <0,01", почему это 0,01 также не округляется здесь? Если бы это было так, уравнение выглядело бы как ожидается (то есть 0.00999999977648258 является не <0,00999999977648258) </p>

РЕДАКТИРОВАТЬ: показан пример кода

Этот код выдаст ошибку. Измените значение с плавающей точкой на реальное, и ошибка «исчезнет» По крайней мере, до временной таблицы.

DECLARE @XMLText varchar(max)

SET @XMLText = 
'<query prodType="1">
  <param type="1" lowMin="10" hiMax="300">
    <item low="18" hi="20" mode="1" weight="1" />
    <item low="220" hi="220" mode="0" weight="1" />
  </param>
  <param type="2" lowMin="4" hiMax="6">
    <item low="5" hi="5" mode="1" weight="1" />
    <item low="6" hi="6" mode="0" weight="0.01" />
  </param>
  <param type="3" lowMin="0" hiMax="300">
    <item low="34" hi="34" mode="1" weight="0.75" />
    <item low="40" hi="60" mode="1" weight="0.25" />
  </param>
</query>'

DECLARE @hxml int, @sp INT, @StartXCount int

EXEC sp_xml_preparedocument @hxml OUTPUT, @XMLText

IF @sp != 0 BEGIN
    SET @Result = '0'
    RETURN
END

DECLARE @t table (
    LowMin         real,
    HiMax          real,
    ParamTypeID    int,
    ParamWeight    float, -- real <<<
    Low            real,
    Hi             real,
    Mode           tinyint          
)

INSERT INTO @t
SELECT      *
FROM        OPENXML (@hxml, '/query/param/item', 2)
WITH        (
                LowMin       real     '../@lowMin',
                HiMax        real     '../@hiMax',
                ParamTypeID  int      '../@type',
                ParamWeight  real     '@weight',
                Low          real     '@low',
                Hi           real     '@hi',
                Mode         tinyint  '@mode'
            )

SELECT * FROM @t

Ответы [ 2 ]

1 голос
/ 24 декабря 2010

Ошибка вызвана тем, что компьютер НЕ МОЖЕТ представляет значение 0,01 с плавающей запятой как с одинарной, так и с двойной точностью.Это значение округляется до ближайшего представимого значения как в формате с плавающей точкой, так и в два раза.Так что в обоих случаях это не 0,01, а отображается только как 0,01 в реальном случае (я не знаю, как работает алгоритм ToString для чисел с плавающей запятой, поэтому не могу сказать, почему он конвертируется в 0,01 в одном случаеи 0,00999999977648258 в другом).

Единственное, что я могу вам точно сказать - в реальном случае оно было округлено до представимого значения выше 0,01, а в двойном случае оно было округлено до представимого значения ниже 0,01.Поэтому проверка не удалась в случае двойной точности.

Чтобы преодолеть эту проблему, вы можете изменить тест проверки на «меньше 0,01 - эпсилон» для очень маленького эпсилона.

1 голос
/ 24 декабря 2010

0.01 не может быть сохранено точно в типе IEEE, так как он не может быть представлен дробью со степенью 2 в знаменателе.

Однако, что я могу воспроизвести, так этонапротив того, что вы говорите:

SELECT  CAST(0.01 AS FLOAT) AS value
FOR XML PATH(''), TYPE

<value>1.000000000000000e-002</value>

SELECT  CAST(0.01 AS REAL) AS value
FOR XML PATH(''), TYPE

<value>9.9999998e-003</value>

Не могли бы вы опубликовать свой точный запрос?

Обновление:

Я получаю те же результаты с вашим кодом: 0,01, когда ParamWeight равен FLOAT, 0,00999999977648258, когда REAL.

Обновление 2:

IEEE типов хранятся в виде знака, мантиссы и значения.Для 32 -битного значения мантисса - это двоичный логарифм наибольшей степени 2 (наименьшего, чем значение), а в значении - это 23 -битная двоичная дробь (число от 1 до 2, ведущий 1 не сохраняется.).

В вашем случае это -7 для мантиссы (2^-7 = 1/128 = 0,0078125) и 1.01000111101011100001010 для значимых и (= 1 + 2348810 / 8388608 = 1,2799999713897705078125).

результирующее число является произведением этих чисел, которое близко к 0.01, но все еще недостаточно близко, чтобы избежать ошибок в 15 -ой цифре (точность которой SQL Server считает важной)

...