1

У меня есть выражение, которое используется для оценки процентилей путем интерполяции между двумя значениями.Есть ли способ улучшить точность с плавающей запятой этого выражения?

windowMin + (currentPercentile - lastPercentile) * (windowMax - windowMin)/(percentile - lastPercentile) 

Это дает мне очень хорошие результаты в реальном мире. Тем не менее, в моих модульных тестах у меня возникают проблемы с тем, что все работает правильно, так как я последовательно получаю значительную ошибку округления.

В трех случаях теста, я стараюсь, чтобы получить 40 тыс, 50 я и 60 я процентиля, в результате этих вычислений:

1 + (0.4 - 0.3333333333333333) * (2 - 1)/(0.6666666666666666 - 0.3333333333333333) 
1 + (0.5 - 0.3333333333333333) * (2 - 1)/(0.6666666666666666 - 0.3333333333333333) 
1 + (0.6 - 0.3333333333333333) * (2 - 1)/(0.6666666666666666 - 0.3333333333333333) 

Это дает:

{ 
    "0.4": 1.2000000000000002, 
    "0.5": 1.5, 
    "0.6": 1.8 
} 

Это не соответствует моему утверждению, которое ищет 1.2 для 40 th percentil е.

Есть ли способ реструктурировать это выражение для повышения точности во всех случаях? Если нет, есть ли простой способ обойти эту проблему с утверждениями chai?

+0

Возможный дубликат [Является ошибкой JavaScript с плавающей запятой?] (Http://stackoverflow.com/questions/588004/is-javascripts-floating-point-math-broken) – Barmar

+0

См. Также http: // stackoverflow. com/questions/2221167/javascript-format-a-rounded-number-to-n-decimals? lq = 1 – Barmar

+2

@Barmar: Это не дубликат. Я спрашиваю о конкретном выражении. Я не прошу объяснений, я прошу смягчить ситуацию. –

ответ

0

случается, что +1,2000000000000002 уже значение двойной точности с плавающей точкой ближайший к точной интерполяции, представленной вами, как показано ниже с помощью выражения Pharo smalltalk (asTrueFraction означает, что значение с плавающей запятой преобразуется в Фракцию, имеющую точно такую ​​же значение)

(1 + ((0.4 asTrueFraction - 0.3333333333333333 asTrueFraction) * (2 - 1)/(0.6666666666666666 asTrueFraction - 0.3333333333333333 asTrueFraction))) asFloat 
-> 1.2000000000000002. 

Даже если оценивать интерполяцию с точной арифметике, мы можем сделать это, заменив asTrueFraction с asMinimalDecimalFraction (которые получают вам десятичное число с минимальным числом цифр, которые будут округлены до одной и той же Float):

0.4 asTrueFraction -> (3602879701896397/9007199254740992). 
0.4 asMinimalDecimalFraction -> (2/5). 
0.3333333333333333 asMinimalDecimalFraction -> (3333333333333333/10000000000000000). 
0.6666666666666666 asMinimalDecimalFraction -> (3333333333333333/5000000000000000). 

Тогда вы получите снова тот же результат, посмотрим, как он разлагается:

(1 + ((0.4 asMinimalDecimalFraction - 0.3333333333333333 asMinimalDecimalFraction) * (2 - 1)/(0.6666666666666666 asMinimalDecimalFraction - 0.3333333333333333 asMinimalDecimalFraction))) 
-> (4000000000000000/3333333333333333). 

(4000000000000000/3333333333333333) asFloat -> 1.2000000000000002. 

другими словами, если вы хотите результат в обращении , то 1.2000000000000002 - лучшая ценность.

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

Измените тест, а не формулу, и вставьте четкие требования к точности.

0

Я вижу два пути решения этой проблемы:

  1. Если вы собираетесь иметь только конечное число в результате вы можете округлить число до некоторой небольшой точности

  2. Отдела путает вас. Умножим обе части equtation к делителю, и вы не будете иметь более бесконечных дробей в результате :)

1

Эти ошибки округления являются характеристикой с плавающей точкой математике.

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

var result = windowMin + (currentPercentile - lastPercentile) * (windowMax - windowMin)/(percentile - lastPercentile); 
return result.toPrecision(6); // returns six significant figures 

или, возможно, toFixed():

return result.toFixed(2); // returns two decimal places. 
+0

Достаточно честный. Однако диапазон моих значений может сильно различаться. Есть ли способ сделать это для определенного количества значащих цифр. В моем случае я бы хотел, чтобы он был в несколько раз меньше, чем 'windowMin - windowMax', который обычно начинается с' 0,0000375'. Итак, в этом случае я хотел бы округлить до 9 цифр. –

+0

'.toPrecision()' принимает параметр, определяющий количество значащих цифр. Вот [ссылка] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision). Конечно, чем больше вы включаете, тем больше вероятность столкнуться с ошибкой округления. –

1

The Chai Closeto предназначен для обработки такого рода теста:

expect(calculatedValue).to.be.closeTo(1.2, 0.000001); 

Первый аргумент ожидаемое значение, а второй представляет собой дельта, указывающее, насколько близко calculatedValue должна быть до 1,2.

Смежные вопросы