1

В примере с суммой

0.1 + 0.2

решается

(0.1*10 + 0.2*10)/10 = 0.3

Как быть с 0.1 * 0.2 = 0.020000000000000004 Вариант с toFixed(n) не подойдет, т.к точность всегда нужна разная, в зависимости от перемножаемых значений

ruslik
  • 533
  • 1
    не знаю зачем и почему это вам нужно, попробуйте по аналогии (0.1*10*0.2*10)/100 – Yura Ivanov Jun 21 '14 at 17:59

2 Answers2

4

Для чисел с плавающей запятой другая арифметика. Связано с тем, что если число однозначно не раскладывается на слагаемые, которые являются степенями двойки, то будет возникать "хвост" цифр после запятой. http://vestikinc.narod.ru/AB/ni_bin.htm

Результат вычисления для чисел с плавающей запятой нельзя сравнивать ни через == ни через ===, потому что этот хвостик может слегка отличаться. При сравнении используется округление до нужной степени точности.

не так: 0.1 + 0.2 ~= 0.3 (приблизительно равно) а вот так: abs((0.1 + 0.2) - 0.3) < 0.0001 , где 0.0001 - требуемая точность


Один из вариантов вычисления точности результата арифметических операций:

Пусть имеем два числа с плавающей запятой A и B. У числа А есть M цифр после запятой, а у B есть K цифр после запятой.

Нужно выполнить 4 операции: сложение, вычисление, деление, умножение.

При вычитании и сложении количество цифр после запятой равно максимуму из M и K:

Пример: число А = 0.111, количество цифр после запятой M = 3; B = 0.0001, количество цифр K = 4; количество цифр после запятой в результате = max(M;K) = max(3;4) = 4

Получается, что результат должен быть с 4 цифрами после запятой, а именно 0.111 + 0.0001 = 0.1111

При умножении А на B количество цифр после запятой равно сумме M и K.

Пример: число А = 0.111, количество цифр после запятой M = 3; B = 0.0001, количество цифр K = 4; количество цифр после запятой в результате = M + K = 3 + 4 = 7

0.111 * 0.0001 = 0.0000111 при округлении до 7 знака после запятой

При делении А на B количество цифр после запятой равно разности M и K. Если результат получился отрицательный, значит число получается целое, без дробной части

Пример: число А = 0.111, количество цифр после запятой M = 3; B = 0.0001, количество цифр K = 4; количество цифр после запятой в результате = M - K = 3 - 4 = -1

0.111 / 0.0001 = 1110 (округление до целых)

Пример: число А = 0.1111, количество цифр после запятой M = 4; B = 0.11, количество цифр K = 2; количество цифр после запятой в результате = M - K = 4 - 2 = 2

0.1111 / 0.11 = 1.01 (округление до 2х цифр после запятой)

С простой арифметикой разобрались ?

Для простоты всегда можно брать количество цифр после запятой в результате как как произведение M * K (корни, степени, логарифмы, тригонометрия), но из-за особенностей преобразования в двоичные числа всё равно может появиться "хвостик" незначащих цифр.


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

copist
  • 2,469
  • 12
  • 9
  • мне нужно получить такой же результат как на калькуляторе для всех чисел, которые нельзя разложить в двоичной системе.
    Имел я ввиду то, что для сложения таких чисел есть решение или хак, как быть с произведением, когда нужна разная степень точности в зависимости от перемножаемых чисел. Вопрос открытый
    – ruslik Jun 22 '14 at 07:28
  • @ruslik, по какому критерию определяется, что вот те четыре многонулевых - это ненужная часть, которая входит в допустимую погрешность? – etki Jun 22 '14 at 07:33
  • 1
    По какому критерию определяет Google калькулятор, что 0.1 * 0.2 = 0.02 ? А 0.4 * 0.4 = 0.16, а не 0.16000000000000003 – ruslik Jun 22 '14 at 07:50
  • дополнил свой ответ примерами вычисления точности для калькулятора – copist Jun 22 '14 at 16:39
  • Ваши размышление конечно хороши, но...

    При вычитании и сложении количество цифр после запятой равно максимуму из M и K:

    ага, конечно - 0.00001 + 0.99999

    пять цифр после запятой? С вычитанием все ещё проще. Там такие примеры сходу сочиняются.

    При умножении А на B количество цифр после запятой равно сумме M и K.

    Например 0.2*0.5 ... Ага, в компании с конечным нулем.

    При делении А на B количество цифр после запятой равно разности M и K. Если результат получился отрицательный, значит число получается целое, без дробной части

    0.1 / 0.03 = 3.333333... - ага, целое, без дробной.

    – KoVadim Jun 22 '14 at 18:43
  • - 0.00001 + 0.99999

    0.99998 не вижу проблемы

    Ага, в компании с конечным нулем

    не вижу проблемы, незначащие нулевые разряды на точность не влияют

    0.1 / 0.03 = 3.333333

    Да, вы правы - http://take.ms/w1z7l . Пусть я ошибся. Возьмите для деления формулу: количество цифр после запятой = сумме M + K. А что даёт калькулятор в windows? Он считает с определённой точностью, в данном случае - 16 цифр после запятой. Исходников калькулятора у меня нет, проверить не могу.

    – copist Jun 22 '14 at 19:03
  • В BigNumber при делении количество цифр после запятой в результате берётся как max(M;K) – copist Jun 22 '14 at 19:14
  • @Pavel Volyntsev Вы перекрутили мой пример. В 0.00001 + 0.99999 минуса нет. Там сложение. А то, что Вы приняли за минус - это тире.

    А что даёт калькулятор в windows?

    У нас обсуждение javascript/google-калькулятора. Да и винда не у всех есть.

    не вижу проблемы, незначащие нулевые разряды на точность не влияют

    А автору вопроса как раз хочется "человеческий результат". Кстати, гугл калькулятор все нули в конце отбросит.

    А BigNumber можно и написать свой, "правильный".

    – KoVadim Jun 22 '14 at 19:34
  • @ruslik, восхитительный ответ. Предлагаю вам решение согласно ему: тяните курлом гугл-калькулятор. – etki Jun 22 '14 at 22:03
  • 1
    @ruslik

    По какому критерию определяет Google калькулятор, что

    0.1 * 0.2 = 0.02

    А 0.4 * 0.4 = 0.16, а не 0.16000000000000003

    по тому же критерию, по которому 0.16000000000000003 +1 = 1.16 в гуглокалькуляторе (округляем по какой-то знак, не знаю точно по какой)

    – zb' Jun 22 '14 at 22:40
  • в общем ваш вариант такой (чтобы было почти как в гугле, правда справа - 16 знаков значащих):
    parseFloat((1/10+2/10).toPrecision(16)) //0.3
    
    
    

    (для справки в гугле 1000000000000001-1000000000000000=0)

    – zb' Jun 22 '14 at 23:50
0

Если Вам нужно "нормальное поведение" (я специально взял в кавычки), то нужно написать (или взять чужую готовую) библиотеку для длинной арифметики. Такую библиотеку написать на самом деле не очень сложно и для разминки мозгов самое оно. Само число можно представить в виде строки (да, строки) или массива цифр + порядок. Сложение/вычитание делается легко, с умножением чуточку сложнее (придется вспомнить, что такое умножение в столбик). Потом деление и корень квадратный. Все остальное будет просто.

Для javascript есть BigNumber.

KoVadim
  • 112,121
  • 6
  • 94
  • 160