为什么这个 MySQL 存储函数给出的结果与在查询中进行计算的结果不同?
这是一个关于使用半正矢公式计算地球上经纬度两点之间距离的问题,用于需要“查找最近的”功能的项目。
这篇文章在 MySQL 中对半正弦公式进行了很好的讨论和解决。
然后我问这个问题关于将其变成存储函数,以便将来的项目可用,而无需查找、记住或重新输入长形式的公式。
一切都很好。除了我的函数在结果上(略有不同)与直接在查询中键入公式之外,所有其他条件都相同。这是为什么呢?
这是我编写的函数:
DELIMITER $$
DROP FUNCTION IF EXISTS haversine $$
CREATE FUNCTION `haversine`
(fromLatitude FLOAT,
fromLongitude FLOAT,
toLatitude FLOAT,
toLongitude FLOAT,
unit VARCHAR(20)
)
RETURNS FLOAT
DETERMINISTIC
COMMENT 'Returns the distance on the Earth between two known points of longitude and latitude'
BEGIN
DECLARE radius FLOAT;
DECLARE distance FLOAT;
IF unit = 'MILES' THEN SET radius = '3959';
ELSEIF (unit = 'NAUTICAL_MILES' OR unit='NM') THEN SET radius = '3440.27694';
ELSEIF (unit = 'YARDS' OR unit='YD') THEN SET radius = '6967840';
ELSEIF (unit = 'FEET' OR unit='FT') THEN SET radius = '20903520';
ELSEIF (unit = 'KILOMETRES' OR unit='KILOMETERS' OR unit='KM') THEN SET radius = '6371.3929';
ELSEIF (unit = 'METRES' OR UNIT='METERS' OR unit='M') THEN SET radius = '6371392.9';
ELSE SET radius = '3959'; /* default to miles */
END IF;
SET distance = (radius * ACOS(COS(RADIANS(fromLatitude)) * COS(RADIANS(toLatitude)) * COS(RADIANS(toLongitude) - RADIANS(fromLongitude)) + SIN(RADIANS(fromLatitude)) * SIN(RADIANS(toLatitude))));
RETURN distance;
END$$
DELIMITER ;
这是一组测试查询,用于查找伦敦眼和白金汉宫之间的距离,仅作为示例。显然,通常您会用您想要比较的地理位置“事物”数据库中的字段替换目的地。
SET @milesModifier = 3959;
SET @myLat = 51.503228;
SET @myLong = -0.119703;
SET @destLat = 51.501267;
SET @destLong = -0.142697;
SELECT @kilometerModifier AS radius,
@myLat AS myLat,
@myLong AS myLong,
@destLat AS destLat,
@destLong AS destLong,
(@milesModifier * ACOS(COS(RADIANS(@myLat)) * COS(RADIANS(@destLat)) * COS(RADIANS(@destLong) - RADIANS(@myLong)) + SIN(RADIANS(@myLat)) * SIN(RADIANS(@destLat)))) AS longFormat,
haversine(@myLat,@myLong,@destLat,@destLong,'MILES') AS distanceMiles,
haversine(@myLat,@myLong,@destLat,@destLong,'NAUTICAL_MILES') AS distanceNautical,
haversine(@myLat,@myLong,@destLat,@destLong,'KM') AS distanceKm,
haversine(@myLat,@myLong,@destLat,@destLong,'METRES') AS distanceMetres,
haversine(@myLat,@myLong,@destLat,@destLong,'YARDS') AS distanceYards,
haversine(@myLat,@myLong,@destLat,@destLong,'FEET') AS distanceFeet,
haversine(@myLat,@myLong,@destLat,@destLong,'') AS distanceDefault
在示例中,我们使用英里 - 因此我们将半径(测试中的@milesModifier,函数中的radius)设置为 3959。
我得到的结果很有趣(在 MySQL 5.2.6 社区版上),重点是:
| longFormat | distanceMiles |
|------------------|-----------------|
| 0.99826000106148 | 0.9982578754425 |
longFormat 是查询中完成的数学运算,distanceMiles 是函数的结果。
结果不同......好吧,所以就在项目中使用该函数而言,它是无关紧要的,但我有兴趣知道函数内部或外部的相同公式如何产生不同的结果。
我猜测这与 FLOAT 的长度有关 - 它们没有在函数中指定,我尝试指定它们(最多 30,15),以便为我拥有的所有数字和输出提供足够的空间我预计 - 但结果仍然略有不同。
This is a question about calculating the distance between two points of latitude and longitude on the earth using a haversine formula, for use in projects where you need to have a 'find my nearest' function.
The haversine formula is well discussed and solved in MySQL in this post.
I then asked this question about turning it into a stored function so that its available for future projects without having to lookup, remember or re-type the formula in its long form.
Its all good. Except my function differs in results (slightly) to just typing the formula directly into the query, all other things being equal. Why is this?
So here's the function I wrote:
DELIMITER $
DROP FUNCTION IF EXISTS haversine $
CREATE FUNCTION `haversine`
(fromLatitude FLOAT,
fromLongitude FLOAT,
toLatitude FLOAT,
toLongitude FLOAT,
unit VARCHAR(20)
)
RETURNS FLOAT
DETERMINISTIC
COMMENT 'Returns the distance on the Earth between two known points of longitude and latitude'
BEGIN
DECLARE radius FLOAT;
DECLARE distance FLOAT;
IF unit = 'MILES' THEN SET radius = '3959';
ELSEIF (unit = 'NAUTICAL_MILES' OR unit='NM') THEN SET radius = '3440.27694';
ELSEIF (unit = 'YARDS' OR unit='YD') THEN SET radius = '6967840';
ELSEIF (unit = 'FEET' OR unit='FT') THEN SET radius = '20903520';
ELSEIF (unit = 'KILOMETRES' OR unit='KILOMETERS' OR unit='KM') THEN SET radius = '6371.3929';
ELSEIF (unit = 'METRES' OR UNIT='METERS' OR unit='M') THEN SET radius = '6371392.9';
ELSE SET radius = '3959'; /* default to miles */
END IF;
SET distance = (radius * ACOS(COS(RADIANS(fromLatitude)) * COS(RADIANS(toLatitude)) * COS(RADIANS(toLongitude) - RADIANS(fromLongitude)) + SIN(RADIANS(fromLatitude)) * SIN(RADIANS(toLatitude))));
RETURN distance;
END$
DELIMITER ;
Here's a set of test queries set to find the distance between the London Eye and Buckingham Palace, just for an example. Obviously normally you'd substitute the destination with fields from your database of geo-located 'things' that you want to compare with.
SET @milesModifier = 3959;
SET @myLat = 51.503228;
SET @myLong = -0.119703;
SET @destLat = 51.501267;
SET @destLong = -0.142697;
SELECT @kilometerModifier AS radius,
@myLat AS myLat,
@myLong AS myLong,
@destLat AS destLat,
@destLong AS destLong,
(@milesModifier * ACOS(COS(RADIANS(@myLat)) * COS(RADIANS(@destLat)) * COS(RADIANS(@destLong) - RADIANS(@myLong)) + SIN(RADIANS(@myLat)) * SIN(RADIANS(@destLat)))) AS longFormat,
haversine(@myLat,@myLong,@destLat,@destLong,'MILES') AS distanceMiles,
haversine(@myLat,@myLong,@destLat,@destLong,'NAUTICAL_MILES') AS distanceNautical,
haversine(@myLat,@myLong,@destLat,@destLong,'KM') AS distanceKm,
haversine(@myLat,@myLong,@destLat,@destLong,'METRES') AS distanceMetres,
haversine(@myLat,@myLong,@destLat,@destLong,'YARDS') AS distanceYards,
haversine(@myLat,@myLong,@destLat,@destLong,'FEET') AS distanceFeet,
haversine(@myLat,@myLong,@destLat,@destLong,'') AS distanceDefault
In the example, we're using miles - so we've set the radius (@milesModifier in the test, radius in the function) to 3959 exactly.
The result I got back was interesting (on MySQL 5.2.6 community edition), highlights:
| longFormat | distanceMiles |
|------------------|-----------------|
| 0.99826000106148 | 0.9982578754425 |
longFormat is the maths done in the query, distanceMiles is the result of the function.
The results are different... OK, so its an insignificance as far as using the function in a project, but i'm interested to know how the same formula inside or outside of the function have different results.
I'm guessing that its to do with lengths of the FLOAT - they're not specified in the function, I have tried specifying them (right up to 30,15) to give plenty of room for all the figures I have and the output I expect - but the results still differ slightly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
FLOAT
是一种近似数据类型 - 请参阅:浮点值问题
数字类型
尝试更改
FLOAT
到DECIMAL(30,15)
以确保您具有正确的精度。如果您想深入讨论浮点,可以尝试这篇文章:
每个计算机科学家都应该了解浮点运算
FLOAT
is an approximate data type - see:Problems with Floating-Point Values
Numeric Types
Try changing
FLOAT
toDECIMAL(30,15)
to ensure that you have the correct precision.If you want an in-depth discussion of floating point, you could try this article:
What Every Computer Scientist Should Know About Floating-Point Arithmetic