Как правильно разделить миниатюрные цифры с двойной точностью без ошибок точности?

Я пытаюсь диагностировать и исправить ошибку, которая сводится к X/Y, что дает неустойчивый результат, когда X и Y малы:

enter image description here

В этом случае как cx, так и patharea плавно увеличиваются. Их отношение является гладкой асимптотой при больших числах, но неустойчивой для "малых" чисел. Очевидная первая мысль состоит в том, что мы достигаем предела точности с плавающей запятой, но сами фактические числа нигде не приближаются к ней. Типы ActionScript "Number" - это поплавки с двойной точностью IEE 754, поэтому должны иметь 15 десятичных цифр точности (если я правильно прочитал).

Некоторые типичные значения знаменателя (patharea):

0.0000000002119123
0.0000000002137313
0.0000000002137313
0.0000000002155502
0.0000000002182787
0.0000000002200977
0.0000000002210072

И числитель (cx):

0.0000000922932995
0.0000000930474444
0.0000000930582124
0.0000000938123574
0.0000000950458711
0.0000000958000159
0.0000000962901528
0.0000000970442977
0.0000000977984426

Каждое из них монотонно возрастает, но соотношение хаотическое, как показано выше.

В больших количествах он опускается до гладкой гиперболы.

Итак, мой вопрос: какой правильный способ иметь дело с очень маленькими цифрами, когда вам нужно разделить друг друга?

Я думал о том, чтобы умножить числитель и/или знаменатель на 1000 заранее, но не смог его полностью выполнить.

Фактический код, о котором идет речь, это recalculate() функция здесь. Он вычисляет центр тяжести многоугольника, но когда многоугольник крошечный, центроид прыгает беспорядочно вокруг места и может оказаться на большом расстоянии от многоугольника. Ряд данных, приведенный выше, является результатом перемещения одного node многоугольника в последовательном направлении (вручную, поэтому он не идеально гладкий).

Это Adobe Flex 4.5.

Ответ 1

Я считаю, что проблема, скорее всего, вызвана следующей строкой в ​​вашем коде:

sc = (lx*latp-lon*ly)*paint.map.scalefactor;

Если ваш многоугольник очень мал, то lx и lon почти такие же, как и ly и latp. Они оба очень большие по сравнению с результатом, поэтому вы вычитаете два числа, которые почти равны.

Чтобы обойти это, мы можем использовать тот факт, что:

x1*y2-x2*y1 = (x2+(x1-x2))*y2 - x2*(y2+(y1-y2))
            = x2*y2 + (x1-x2)*y2 - x2*y2 - x2*(y2-y1)
            = (x1-x2)*y2 - x2*(y2-y1)

Итак, попробуйте следующее:

dlon = lx - lon
dlat = ly - latp
sc = (dlon*latp-lon*dlat)*paint.map.scalefactor;

Значение математически одинаково, но термины на порядок меньше, поэтому ошибка также должна быть на порядок меньше.

Ответ 2

Джеффри Сакс правильно определил основную проблему - потерю точности от объединения терминов, которые (намного) больше конечного результата. Предлагаемая переписывание устраняет часть проблемы - очевидно, достаточную для реального случая, учитывая счастливый ответ.

Однако вы можете обнаружить, что, если многоугольник станет снова (намного) меньше и/или удаляться от начала координат, снова появится неточность. В переписанной формуле термины все еще немного больше их разницы.

Кроме того, в алгоритме имеется еще одна комбинация "больших и сопоставимых чисел с разными знаками". Различные значения "sc" в последующих циклах итерации по краям многоугольника эффективно объединяются в конечное число, которое (намного) меньше индивидуального sc (i). (если у вас выпуклый многоугольник, вы обнаружите, что существует одна непрерывная последовательность положительных значений и одна непрерывная последовательность отрицательных значений, в невыпуклых многоугольниках отрицательные и положительные элементы могут быть переплетены).

Что алгоритм делает, эффективно, вычисляет площадь многоугольника, добавляя области треугольников, натянутых на края и начало координат, где некоторые из членов отрицательны (всякий раз, когда ребро проходит по часовой стрелке, просматривая его из происхождение) и некоторый положительный (против часовой стрелки ход по краю).

Вы избавляетесь от ВСЕХ проблем потери точности, определяя происхождение в одном из углов многоугольника, скажем (lx, ly), а затем добавляя треугольные поверхности, натянутые по краям и этот угол (так: преобразование lon to (lon-lx) и latp to (latp-ly) - с дополнительным бонусом, который вам нужно обработать двумя треугольниками меньше, потому что, очевидно, края, которые ссылаются на выбранный исходный угол, дают нулевые поверхности.

Для области-части все. Для центральной части вам, конечно, придется "преобразовать" результат в исходный кадр, то есть добавить (lx, ly) в конец.