开发者

FP precision error on converting XML -> Table & processing it

In SQL Server 2008, I have the value "0.01" in an XML attribute. Using OPENXML, I shred the XML into a temp table. If the applicable column is of type real (single precision), it comes out as 0.01 in the table. Good. However, if the precision is float (double precision), it comes out as 0.00999999977648258. This makes no sense. Why is it doing this?

My next question is that regardless of how the value is represented in the temp table, when I run an aggregate function on it, it always comes back as 0.00999999977648258. This is causing validation errors: the procedure is reporting the input is too small (< 0.01), which is not true.

Any ideas why these rounding errors are happening and how to overcome them?

Already tried: make the column a varchar.

EDIT2:

Based on answers, I understand the problem is due to the fact that IEEE numbers cannot represent 0.01 exactly. Therefor my next question:

"WHERE {computed} < 0.01", why is that 0.01 not also being rounded here? If it were, the equation would eval as expected (i.e. 0.00999999977648258 is not < 0.00999999977648258)

EDIT: Sample code shown

This code will produce the error. Change the indicated float to real & the error "disappears". At least so far as the temp table goes.

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开发者_JS百科.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


0.01 can't be stored exactly in an IEEE type, since it's not representable with a fraction with a power of 2 in denominator.

However, what I can reproduce is the opposite of what you are saying:

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>

Could you please post your exact query?

Update:

I get the same results with your code: 0,01 when ParamWeight is FLOAT, 0,00999999977648258 when it's REAL.

Update 2:

IEEE types are stored as a sign, mantissa and a significand. For a 32-bit value, mantissa is the binary logarithm of the greatest power of 2 (least than the value), and a significand is a 23-bit binary fraction (a number from 1 to 2, the leading 1 is not stored.).

In your case it's -7 for the mantissa (2^-7 = 1/128 = 0,0078125), and 1.01000111101011100001010 for the significand (= 1 + 2348810 / 8388608 = 1,2799999713897705078125).

The resulting number is a product of these numbers which is close to 0.01 but still not close enough to avoid errors in 15'th digit (which precision SQL Server considers important)


The error is caused because the computer CANNOT represent the value 0.01 in floating point both in single and double precision. This value is rounded to the nearest representable value both in float and in double. So in both cases it's not 0.01, but only displayed to you as 0.01 in the real case (I don't know how does the ToString algorithm for floating point work, so can't tell you why it is converted to 0.01 in one case and 0.00999999977648258 in another).

The only thing I can tell you for sure - in the real case it was rounded to a representable value ABOVE 0.01 and in the double case it was rounded to a representable value BELOW 0.01. Therefore validation failed in the double precision case.

To overcome this problem you can change your validation test to be "smaller than 0.01 - epsilon" for some very small epsilon.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜