Почему эта хранимая функция MySQL дает результаты, отличные от вычислений в запросе? - PullRequest
3 голосов
/ 17 июня 2011

Это вопрос о расчете расстояния между двумя точками широты и долготы на земле по формуле haversine, для использования в проектах, где вам нужна функция «найди мое ближайшее».

Формула haversine хорошо обсуждается и решается в 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), чтобы освободить место для всех цифр и выходных данныхЯ ожидаю - но грезультаты все еще немного отличаются.

1 Ответ

5 голосов
/ 23 июня 2011

FLOAT - приблизительный тип данных - см .:

Проблемы со значениями с плавающей точкой
Числовые типы

Попробуйте изменить FLOAT на DECIMAL(30,15), чтобы обеспечить правильную точность.

Если вы хотите подробно обсудить с плавающей запятой, вы можете попробовать эту статью:

Что должен знать каждый компьютерщик об арифметике с плавающей точкой

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...