来自 MariaDB POLYGON 的数据不正确 SELECT
Incorrect data from MariaDB POLYGON SELECT
服务器:MariaDB 10.4.17
插入小数点右侧 14 位的 POLYGON,然后选择相同的数据,returns 是 15[的 POLYGON小数点右边数,比实际存在的数据多,超精度不正确
插入一个小数点右边有 15 位数字的 0 填充 POLYGON,然后选择相同的数据,returns 一个小数点右边有 15 位数字的 POLYGON,但是 SELECTed 数据最后一位数字不正确,不是用于右填充的 0。
由于 table 数据不正确,因此 ST_Contains() 等各种几何函数会产生不正确的结果。这似乎是某种浮点类型的错误,但我不确定如何解决它。
有什么方法可以让 MariaDB 保存、使用和return提供的相同数据?
示例:
INSERT INTO `Area`
(`Name`, `Coords`)
VALUES ('Test ', GeomFromText('POLYGON((
-76.123527198020080 43.010597920077250,
-76.128263410842290 43.016193091211520,
-76.130763247573610 43.033194256815040,
-76.140676208063910 43.033514863935440,
-76.13626333248750 43.008550330099250,
-76.123527198020080 43.010597920077250))'));
SELECT Coords FROM `Area` WHERE `Name` = 'Test';
POLYGON ((
-76.123527198020085 43.010597920077252,
-76.128263410842294 43.01619309121152,
-76.130763247573611 43.033194256815037,
-76.140676208063908 43.033514863935437,
-76.136263332487502 43.008550330099247,
-76.123527198020085 43.010597920077252
))
编辑:
按照@Michael-Entin 的说法,浮点错误是一个死胡同,不能对我得到的错误的大小负责。
更新:
问题是“我”。我不小心在其中一个查询中使用了 MBRContains() 而不是 ST_Contains().
MBRContains 使用将包含多边形的“最小边界矩形”,而不是实际的 POLYGON 坐标。
使用 MBRContains 导致区域明显大于预期,似乎是处理错误,但事实并非如此。
ST_Contains() 速度较慢,但尊重所有 POLYGON 边并产生正确的结果。
感谢@Michael-Entin 注意到浮点错误无法解释我遇到的错误的严重程度。这些信息为我指明了正确的方向。
我认为你的精度已经达到了64位浮点数的极限,你得到的实际上是CPU所表示的最接近的浮点值。
下面的代码打印输入值而不做任何修改,然后以尽可能小的数量递减和递增下一个双精度浮点值:
int main() {
const double f = -76.123527198020080;
cout << setprecision(17) << f << endl
<< nextafter(f, -INFINITY) << endl
<< nextafter(f, INFINITY) << endl;
}
我得到的结果
-76.123527198020085
-76.123527198020099
-76.123527198020071
如您所见,-76.123527198020085 是最接近您的坐标 -76.123527198020080 的值,其最近的可能邻居是 -76.123527198020099(甚至更远)和 -76.123527198020071(也稍远,但方向不同)。
所以我认为没有任何方法可以保持您想要的精度。也不应该有实际的理由来保持这种精度(差异小于一微米,即一米的 1e-6)。
您应该关注的是 ST_Contains
到底有多不符合您的期望。几何库通常使用略高于坐标数值精度的公差距离进行捕捉,理想情况下应该确保输入值的这种微小差异不会影响此类函数的结果。
大多数浮点硬件都以 2 为基数。
如果我们尝试分解以 2 为基数的 -76.128263410842290 的绝对值,则为:
64 (2^6) + 8 (2^3) + 4 (2^2) + 0.125 (2^-3) + ...
我们可以通过某种方式在基数为 1001100.001 的位序列中记下这个数字...
运气不好,在基数 2 中,这个数字需要这样的位的无限序列。
序列开始于:
1001100.001000001101010111011110111100101101011101001110111000...
但是浮点数的精度有限,有效数在 IEEE 双精度中只有 53 位,包括小数分隔符之前的位。
这意味着最低有效位(精度最低的单位)表示 2^-46...
1001100.001000001101010111011110111100101101011101001110111000...
1001100.00100000110101011101111011110010110101110101
请注意,浮点值已向上舍入(到最接近的浮点数)。
让我们将 2^-46 乘以 5^46/5^46 的适当次方:它是 5^46/10^46。
这意味着它的 DECIMAL 表示恰好在 DECIMAL 点之后的 46 位结束,或者如果 float significand 的尾随位为零(这里不是这种情况,尾随位为 1)则少一点。
因此,这些浮点数的小数部分可能有大约 46 位,甚至不像您假设的那样是 14 位或 15 位。
如果我们将这个浮点值转回十进制,我们确实得到:
-76.12826341084229397893068380653858184814453125
-76.128263410842290
看到它确实比您在此处的初始输入略大,因为浮点数已向上取整。
如果您要求在分数分隔符后打印 15 位小数,您会得到一个四舍五入的结果。
-76.128263410842294
在这个浮点数中,最后一位2^-46是十进制值
0.0000000000000142108547152020037174224853515625
其中 142108547152020037174224853515625 是 5^46,你可以算一下。
最后一位的立即浮点值会有所不同(我们可以加减)
1001100.00100000110101011101111011110010110101110100
1001100.00100000110101011101111011110010110101110101
1001100.00100000110101011101111011110010110101110110
表示直接浮点数邻居大约+/- 1.42 10^-14进一步...
这意味着你不能相信分数后的第14位数字,双精度没有这样的分辨率!
不足为奇的是,最近的浮点数有时会比您指定的输入低 7 10^-15(分辨率的一半,这要归功于四舍五入到最近的规则)。
请记住,float 精度是相对的,如果我们消耗小数分隔符的剩余位,我们会降低小数部分的精度(该点实际上是浮动的)。
这是科学家在使用浮点数之前应该掌握的非常基本的知识。
我希望这些示例作为非常有限的介绍有所帮助。
服务器:MariaDB 10.4.17
插入小数点右侧 14 位的 POLYGON,然后选择相同的数据,returns 是 15[的 POLYGON小数点右边数,比实际存在的数据多,超精度不正确
插入一个小数点右边有 15 位数字的 0 填充 POLYGON,然后选择相同的数据,returns 一个小数点右边有 15 位数字的 POLYGON,但是 SELECTed 数据最后一位数字不正确,不是用于右填充的 0。
由于 table 数据不正确,因此 ST_Contains() 等各种几何函数会产生不正确的结果。这似乎是某种浮点类型的错误,但我不确定如何解决它。
有什么方法可以让 MariaDB 保存、使用和return提供的相同数据?
示例:
INSERT INTO `Area`
(`Name`, `Coords`)
VALUES ('Test ', GeomFromText('POLYGON((
-76.123527198020080 43.010597920077250,
-76.128263410842290 43.016193091211520,
-76.130763247573610 43.033194256815040,
-76.140676208063910 43.033514863935440,
-76.13626333248750 43.008550330099250,
-76.123527198020080 43.010597920077250))'));
SELECT Coords FROM `Area` WHERE `Name` = 'Test';
POLYGON ((
-76.123527198020085 43.010597920077252,
-76.128263410842294 43.01619309121152,
-76.130763247573611 43.033194256815037,
-76.140676208063908 43.033514863935437,
-76.136263332487502 43.008550330099247,
-76.123527198020085 43.010597920077252
))
编辑:
按照@Michael-Entin 的说法,浮点错误是一个死胡同,不能对我得到的错误的大小负责。
更新:
问题是“我”。我不小心在其中一个查询中使用了 MBRContains() 而不是 ST_Contains().
MBRContains 使用将包含多边形的“最小边界矩形”,而不是实际的 POLYGON 坐标。
使用 MBRContains 导致区域明显大于预期,似乎是处理错误,但事实并非如此。
ST_Contains() 速度较慢,但尊重所有 POLYGON 边并产生正确的结果。
感谢@Michael-Entin 注意到浮点错误无法解释我遇到的错误的严重程度。这些信息为我指明了正确的方向。
我认为你的精度已经达到了64位浮点数的极限,你得到的实际上是CPU所表示的最接近的浮点值。
下面的代码打印输入值而不做任何修改,然后以尽可能小的数量递减和递增下一个双精度浮点值:
int main() {
const double f = -76.123527198020080;
cout << setprecision(17) << f << endl
<< nextafter(f, -INFINITY) << endl
<< nextafter(f, INFINITY) << endl;
}
我得到的结果
-76.123527198020085
-76.123527198020099
-76.123527198020071
如您所见,-76.123527198020085 是最接近您的坐标 -76.123527198020080 的值,其最近的可能邻居是 -76.123527198020099(甚至更远)和 -76.123527198020071(也稍远,但方向不同)。
所以我认为没有任何方法可以保持您想要的精度。也不应该有实际的理由来保持这种精度(差异小于一微米,即一米的 1e-6)。
您应该关注的是 ST_Contains
到底有多不符合您的期望。几何库通常使用略高于坐标数值精度的公差距离进行捕捉,理想情况下应该确保输入值的这种微小差异不会影响此类函数的结果。
大多数浮点硬件都以 2 为基数。
如果我们尝试分解以 2 为基数的 -76.128263410842290 的绝对值,则为:
64 (2^6) + 8 (2^3) + 4 (2^2) + 0.125 (2^-3) + ...
我们可以通过某种方式在基数为 1001100.001 的位序列中记下这个数字...
运气不好,在基数 2 中,这个数字需要这样的位的无限序列。
序列开始于:
1001100.001000001101010111011110111100101101011101001110111000...
但是浮点数的精度有限,有效数在 IEEE 双精度中只有 53 位,包括小数分隔符之前的位。
这意味着最低有效位(精度最低的单位)表示 2^-46...
1001100.001000001101010111011110111100101101011101001110111000...
1001100.00100000110101011101111011110010110101110101
请注意,浮点值已向上舍入(到最接近的浮点数)。
让我们将 2^-46 乘以 5^46/5^46 的适当次方:它是 5^46/10^46。
这意味着它的 DECIMAL 表示恰好在 DECIMAL 点之后的 46 位结束,或者如果 float significand 的尾随位为零(这里不是这种情况,尾随位为 1)则少一点。
因此,这些浮点数的小数部分可能有大约 46 位,甚至不像您假设的那样是 14 位或 15 位。
如果我们将这个浮点值转回十进制,我们确实得到:
-76.12826341084229397893068380653858184814453125
-76.128263410842290
看到它确实比您在此处的初始输入略大,因为浮点数已向上取整。
如果您要求在分数分隔符后打印 15 位小数,您会得到一个四舍五入的结果。
-76.128263410842294
在这个浮点数中,最后一位2^-46是十进制值
0.0000000000000142108547152020037174224853515625
其中 142108547152020037174224853515625 是 5^46,你可以算一下。
最后一位的立即浮点值会有所不同(我们可以加减)
1001100.00100000110101011101111011110010110101110100
1001100.00100000110101011101111011110010110101110101
1001100.00100000110101011101111011110010110101110110
表示直接浮点数邻居大约+/- 1.42 10^-14进一步...
这意味着你不能相信分数后的第14位数字,双精度没有这样的分辨率!
不足为奇的是,最近的浮点数有时会比您指定的输入低 7 10^-15(分辨率的一半,这要归功于四舍五入到最近的规则)。
请记住,float 精度是相对的,如果我们消耗小数分隔符的剩余位,我们会降低小数部分的精度(该点实际上是浮动的)。
这是科学家在使用浮点数之前应该掌握的非常基本的知识。
我希望这些示例作为非常有限的介绍有所帮助。