sql select calc return long decimals [duplicate]

Рассмотрим следующий код:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Почему происходят эти неточности?

2279
задан 26 May 2018 в 14:59

22 ответа

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

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

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

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

9
ответ дан 15 August 2018 в 16:53

Учитывая, что никто не упомянул об этом ...

Некоторые языки высокого уровня, такие как Python и Java, поставляются с инструментами для преодоления ограничений двоичной с плавающей запятой. Например:

модуль decimal Python и класс BigDecimal Java, которые представляют числа внутри с десятичной нотацией (в отличие от двоичной нотации). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с бинарной арифметикой с плавающей запятой. Десятичные числа очень хороши при работе с деньгами: десять центов плюс двадцать центов всегда ровно тридцать центов:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Модуль Python decimal основан на стандарте IEEE 854-1987. Python fractions и класс Apache Common BigFraction. Оба представляют собой рациональные числа как пары (numerator, denominator), и они могут давать более точные результаты, чем десятичная арифметика с плавающей запятой.

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

12
ответ дан 15 August 2018 в 16:53

Другой вопрос был назван дубликатом этого:

В C ++ почему результат cout << x отличается от значения, которое показывает отладчик для x?

x в вопросе является переменной float.

В качестве примера можно привести

float x = 9.9F;

Отладчик показывает 9.89999962, вывод cout 9.9.

Ответ оказывается, что точность cout по умолчанию для float равна 6, поэтому округляет до шести десятичных цифр.

См. здесь для справки

1
ответ дан 15 August 2018 в 16:53

Math.sum (javascript) .... вид замены оператора

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    diff:{
        value: function(A,B){
            var prec = this.max(this.get_precision(A),this.get_precision(B));
            return +this.precision(A-B,prec);
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

Идея состоит в том, чтобы вместо Math использовать операторы, чтобы избежать ошибок с плавающей запятой [ ! d3]

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

также отмечают, что Math.diff и Math.sum автоматически определяют точность использования

Math.sum принимает любое количество аргументов

0
ответ дан 15 August 2018 в 16:53

Было опубликовано много хороших ответов, но я хотел бы добавить еще один.

Не все числа могут быть представлены с помощью float / double. Например, будет представлено число «0,2» как «0.200000003» в единой точности в стандарте по плавающей точке IEEE754.

Модель для хранения действительных чисел под капотом представляет собой число с плавающей запятой как

тип 0.2 легко, FLT_RADIX и DBL_RADIX равно 2; не 10 для компьютера с FPU, который использует «стандарт IEEE для двоичной арифметики с плавающей запятой» (ISO / IEEE Std 754-1985) ».

Так что это очень сложно точно представить такие числа. Даже если вы укажете эту переменную явно без какого-либо промежуточного вычисления.

26
ответ дан 15 August 2018 в 16:53

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

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

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

Теперь, как бы вы отделили все кусочки таким образом, чтобы это было бы до одного (0,1) или одну пятую (0,2) пиццы? На самом деле подумайте об этом и попробуйте разобраться. Вы даже можете попытаться использовать настоящую пиццу, если у вас есть мифическая пресса для резки пиццы под рукой. : -)

Большинство опытных программистов, конечно же, знают реальный ответ, который заключается в том, что нет возможности объединить 53 раза десятый или пятую часть пиццы, используя эти кусочки, независимо от того, насколько мелко вы их нарезаете. Вы можете сделать довольно хорошее приближение, и если вы добавите аппроксимацию 0,1 с аппроксимацией 0,2, вы получите довольно хорошее приближение 0,3, но это все равно только приближение.

Для двойного -оценки (это точность, которая позволяет вам вдвое сократить вашу пиццу 53 раза), цифры сразу меньше и больше 0,1 - 0.09999999999999999167332731531132594682276248931884765625 и 0,1000000000000000055511151231257827021181583404541015625. Последнее немного ближе к 0,1, чем первое, поэтому числовой синтаксический анализатор, учитывая ввод 0,1, благоприятствует последнему.

(Разница между этими двумя числами - это «самый маленький срез», который мы должны решить либо включить, что вводит восходящее смещение, либо исключить, что приводит к смещению вниз. Техническим термином для этого наименьшего среза является ulp.)

В случае 0.2 цифры все же, просто увеличен в 2 раза. Мы также рекомендуем значение, которое немного больше 0,2.

Обратите внимание, что в обоих случаях аппроксимации для 0,1 и 0,2 имеют небольшое смещение вверх. Если мы добавим достаточно этих предубеждений, они будут толкать число дальше и дальше от того, что мы хотим, а на самом деле, в случае 0,1 + 0,2, смещение достаточно велико, чтобы получившееся число больше не было самым близким числом до 0,3.

[D15] в частности, 0,1 + 0,2 действительно 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, тогда как число ближе к 0,3 на самом деле 0,299999999999999988897769753748434595763683319091796875.

[D16] [D17] П.С. Некоторые языки программирования также предоставляют резаки для пиццы, которые могут ulp . Хотя такие резаки для пиццы необычны, если у вас есть доступ к одному, вы должны использовать его, когда важно получить ровно одну десятую или одну пятую части среза.

(Первоначально опубликовано в Quora.)

227
ответ дан 15 August 2018 в 16:53
  • 1
    Обратите внимание, что есть некоторые языки, которые включают точную математику. Одним из примеров является схема, например, через GNU Guile. См. [D0] draketo.de/english/exact-math-to-the-rescue - они сохраняют математику как фракции и только разрезают в конце. – Arne Babenhauserheide 20 November 2014 в 10:40
  • 2
    @FloatingRock На самом деле, очень немногие основные языки программирования имеют встроенные рациональные числа. Арне - это Schemer, как и я, поэтому это вещи, которые мы испортили. – Chris Jester-Young 25 November 2014 в 20:56
  • 3
    @ArneBabenhauserheide Я думаю, стоит добавить, что это будет работать только с рациональными числами. Поэтому, если вы делаете математику с иррациональными цифрами, такими как pi, вам придется хранить ее как кратную pi. Конечно, любой расчет с участием pi не может быть представлен как точное десятичное число. – Aidiakapi 11 March 2015 в 17:06
  • 4
    @connexo Хорошо. Как бы вы запрограммировали ротатор пиццы на 36 градусов? Что такое 36 градусов? (Подсказка: если вы в состоянии точно определить это, у вас также есть кусочек для резки пиццы, то есть десятый). Другими словами, вы не можете иметь 1/360 (градус) или 1 / 10 (36 градусов) с только двоичной плавающей точкой. – Chris Jester-Young 13 August 2015 в 17:50
  • 5
    @connexo Кроме того, "каждый идиот" не может вращать пиццу ровно 36 градусов. Люди слишком склонны к ошибкам, чтобы сделать что-то совершенно точное. – Chris Jester-Young 13 August 2015 в 17:51

Нет, не разбито, но большинство десятичных дробей должно быть аппроксимировано

Сводка

Арифметика с плавающей точкой является точной, к сожалению, она не совпадает с нашей обычной базой- 10, так что получается, что мы часто даем ему ввод, который немного от того, что мы написали.

Даже простые числа, такие как 0.01, 0.02, 0.03, 0.04 ... 0.24, не представляются точно так же, как бинарные фракции, даже если у вас были тысячи бит точности в мантиссе, даже если у вас были миллионы. Если вы отсчитываете с шагом 0,01, пока вы не достигнете 0,25, вы получите первую фракцию (в этой последовательности), представленную в base10 и base2. Но если вы попытались использовать FP, ваш 0,01 был бы слегка отключен, поэтому единственный способ добавить 25 из них до хорошего точного 0.25 потребовал бы длинной цепи причинности, включающей защитные биты и округление. Трудно предсказать, чтобы мы подняли руки и сказали, что -

Мы постоянно даем аппаратное обеспечение FP, что кажется простым в базе 10, но является повторяющейся фракцией в базе 2. [ ! d10]

Резюме

Как это произошло?

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

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; Как это произошло?

В двоичном коде мы получаем только 2n-член, то есть:

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; ; & NBSP; & NBSP; & NBSP; & NBSP; x / 2n

Итак, в десятичной форме мы не можем представить 1 / 10 . Поскольку база 10 включает в себя 2 в качестве основного фактора, каждое число, которое мы можем записать как двоичную дробь 2n , можно записать в виде базовой дроби. Однако вряд ли что-либо, что мы пишем как base10, представляется в двоичном виде. В диапазоне от 0,01, 0,02, 0,03 ... 0,99 только три числа могут быть представлены в нашем формате FP: 0,25, 0,50 и 0,75, поскольку они составляют 1/4, 1/2 и 3/4, все числа с простым множителем, использующим только 2n-член.

В базе 10 мы не можем представить 1 / 2 . Но в двоичном коде мы не можем сделать 1/10 x / 2n 1/3.

Резюме

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

Разработчикам обычно поручают делать три сравнения , лучше советовать округлять до целых значений (в библиотеке C: round ( ) и roundf (), т. е. оставаться в формате FP), а затем сравнивать. Округление до определенной длины десятичной дробей решает большинство проблем с выходом.

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

Вся проблема действительно возникает, когда люди пытаются использовать FP для подсчета бобов. Это работает для этого, но только если вы придерживаетесь интегральных значений, какой вид поражает смысл его использования. Вот почему у нас есть все эти библиотеки программного обеспечения с десятичной дроби.

Резюме

Мне нравится ответ «Пицца» Криса, потому что он описывает фактическую проблему, а не только обычную ручную работу о «неточности». Если бы FP были просто «неточными», мы могли бы исправить это и сделали бы это несколько десятилетий назад. Причина, по которой у нас нет, - это то, что формат FP компактен и быстр, и это лучший способ хрустить множество чисел. Кроме того, это наследие космической эры и гонки вооружений и ранние попытки решить большие проблемы с очень медленными компьютерами с использованием небольших систем памяти. (Иногда отдельные магнитные сердечники для 1-битного хранения, но это уже другая история.)

81
ответ дан 15 August 2018 в 16:53
  • 1
    Мой ответ был отклонен вскоре после его публикации. С тех пор я сделал много изменений (включая явное указание повторяющихся битов при записи 0,1 и 0,2 в двоичном формате, которые я пропустил в оригинале). В случае непредвиденного того, что наблюдатель видит это, не могли бы вы дать мне несколько отзывов, чтобы я мог улучшить свой ответ? Я чувствую, что мой ответ добавляет что-то новое, поскольку обработка суммы в IEEE 754 не рассматривается таким же образом в других ответах. Хотя «Что должен знать каждый компьютерный ученый ...» охватывает один и тот же материал, мой ответ рассматривает специально с случаем 0,1 + 0,2. – Wai Ha Lee 24 February 2015 в 11:29
  • 2
    Округление до ближайшего целого не является безопасным способом решения проблемы сравнения во всех случаях. 0.4999998 и 0.500001 округляют до разных целых чисел, поэтому существует «опасная зона», вокруг каждой граничной точки. (Я знаю, что эти десятичные строки, вероятно, не точно представлены как бинарные поплавки IEEE.) – Peter Cordes 9 December 2016 в 07:31
  • 3
    Кроме того, несмотря на то, что с плавающей точкой является «наследием», формат, он очень хорошо разработан. Я не знаю ничего, что кто-то изменил бы, если бы перепроектировал его сейчас. Чем больше я узнаю об этом, тем больше я думаю, что это действительно хорошо . например смещенный показатель означает, что последовательные двоичные поплавки имеют последовательные целочисленные представления, поэтому вы можете реализовать nextafter() с целым приращением или декрементом в двоичном представлении поплавка IEEE. Кроме того, вы можете сравнить float как целые числа и получить правильный ответ, за исключением случаев, когда они оба отрицательные (из-за знака-величины по сравнению с дополнением 2). – Peter Cordes 9 December 2016 в 07:35
  • 4
    Я не согласен, поплавки должны храниться как десятичные, а не двоичные, и все проблемы решаются. – Ronen Festinger 19 February 2017 в 23:32
  • 5
    Не должно & lt; x / (2 ^ n + 5 ^ n) " быть " x / (2 ^ n * 5 ^ n) "? – Wai Ha Lee 5 February 2018 в 11:34
  • 6
    @RonenFestinger - примерно 1/3? – Stephen C 15 August 2018 в 06:32

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

Например:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... вместо:

var result = 0.1 + 0.2;     // result === 0.3 returns false

Выражение 0.1 + 0.2 === 0.3 возвращает false в JavaScript, но, к счастью, целочисленная арифметика в плавающей точке является точной, поэтому ошибки масштабирования можно избежать путем масштабирования.

В качестве практического примера, чтобы избежать проблем с плавающей запятой, где точность имеет первостепенное значение, рекомендуется обрабатывать деньги как целое число, представляющее число центов: 2550 центов вместо 25.50 долларов.

1 Douglas Crockford: JavaScript: Хорошие детали: Приложение A - Ужасные части (стр. 105).

99
ответ дан 15 August 2018 в 16:53
  • 1
    Проблема в том, что само преобразование неточно. 16.08 * 100 = 1607.9999999999998. Нужно ли прибегать к расщеплению числа и преобразованию отдельно (как в 16 * 100 + 08 = 1608)? – Jason 8 October 2011 в 00:13
  • 2
    Решение здесь состоит в том, чтобы делать все ваши вычисления в целых числах, а затем делить на вашу долю (100 в этом случае) и округлять только при представлении данных. Это гарантирует, что ваши расчеты всегда будут точными. – David Granado 9 December 2011 в 03:38
  • 3
    Просто немного вычислить: целочисленная арифметика точна только с плавающей точкой до точки (предназначенная для каламбура). Если число больше, чем 0x1p53 (для использования шестнадцатеричной нотации с плавающей запятой Java 7 = 9007199254740992), тогда ulp равно 2 в этой точке и поэтому 0x1p53 + 1 округляется до 0x1p53 (и 0x1p53 + 3 округляется до 0x1p53 + 4, из-за округления до четности). :-D Но, конечно, если ваш номер меньше 9 квадриллионов, вы должны быть в порядке. :-П – Chris Jester-Young 3 December 2014 в 17:28
  • 4
    Итак, как вы можете .1 + .2 показать .3? – CodyBugstein 21 June 2015 в 08:58
  • 5
    Джейсон, вы должны просто округлить результат (int) (16.08 * 100 + 0.5) – Mikhail Semenov 23 December 2015 в 13:10

Ошибки округления с плавающей запятой. 0,1 не могут быть представлены точно в базе-2, как в базе-10, из-за недостающего простого коэффициента 5. Так же, как 1/3 принимает бесконечное число цифр для представления в десятичной форме, но составляет «0,1» в базе-3, 0.1 принимает бесконечное число цифр в базе-2, где оно не находится в базе-10. И у компьютеров нет бесконечного объема памяти.

199
ответ дан 15 August 2018 в 16:53
  • 1
    компьютерам не требуется бесконечное количество памяти, чтобы получить 0,1 + 0,2 = 0,3 справа – Pacerier 15 October 2011 в 21:27
  • 2
    @Pacerier Конечно, они могли бы использовать два целых числа без ограничения точности для представления доли, или они могли бы использовать нотацию цитирования. Это конкретное понятие "двоичного" или "десятичный" что делает это невозможным - идея о том, что у вас есть последовательность двоичных / десятичных цифр и где-то там, точка счисления. Чтобы получить точные рациональные результаты, нам нужен лучший формат. – Devin Jeanpierre 16 October 2011 в 00:45
  • 3
    @Pacerier: ни двоичная, ни десятичная плавающая точка не могут точно хранить 1/3 или 1/13. Десятичные типы с плавающей запятой могут точно представлять значения формы M / 10 ^ E, , но менее точные, чем двоичные числа с плавающей запятой с одинаковым размером, когда речь идет о представлении большинства других фракций . Во многих приложениях более полезно иметь более высокую точность с произвольными фракциями, чем иметь идеальную точность с несколькими «специальными» из них. – supercat 24 April 2014 в 21:43
  • 4
    @Pacerier Они делают , если они хранят числа в виде двоичных поплавков, что и было точкой ответа. – Mark Amery 15 August 2014 в 03:04
  • 5
    @chux: разница в точности между двоичными и десятичными типами невелика, но разница 10: 1 в наилучшей ситуации против наихудшей точности для десятичных типов намного больше, чем разница 2: 1 с двоичными типами. Мне любопытно, есть ли у кого-либо встроенное аппаратное или письменное программное обеспечение для эффективной работы с любым из десятичных типов, поскольку ни один из них не будет казаться эффективным для реализации на аппаратном и программном обеспечении. – supercat 26 August 2015 в 22:47

Когда вы конвертируете .1 или 1/10 в base 2 (двоичный), вы получаете повторяющийся шаблон после десятичной точки, точно так же, как пытаться представить 1/3 в базе 10. Значение не является точным, и поэтому вы можете 't делать точную математику с ним, используя обычные методы с плавающей запятой.

359
ответ дан 15 August 2018 в 16:53
  • 1
    Большой и короткий ответ. Повторяющийся рисунок выглядит как 0.00011001100110011001100110011001100110011001100110011 ... – Konstantin Chernov 16 June 2012 в 19:22
  • 2
    Это не объясняет, почему не лучший алгоритм, который не преобразуется в двоичные файлы на первом месте. – Dmitri Zaitsev 10 May 2016 в 17:43
  • 3
    Потому что производительность. Использование двоичного файла в несколько тысяч раз быстрее, потому что оно является родным для машины. – Joel Coehoorn 10 May 2016 в 22:30
  • 4
    Существуют методы ARE, которые дают точные десятичные значения. BCD (двоично-кодированное десятичное число) или различные другие формы десятичного числа. Однако они медленнее (медленнее LOT) и занимают больше памяти, чем при использовании двоичной с плавающей запятой. (в качестве примера, упакованный BCD хранит 2 десятичных разряда в байте. Это 100 возможных значений в байте, которые могут фактически хранить 256 возможных значений или 100/256, что составляет около 60% возможных значений байта.) – Duncan C 21 June 2016 в 19:43
  • 5
    @Jacksonkr вы все еще думаете в базе-10. Компьютеры являются базовыми 2. – Joel Coehoorn 14 November 2016 в 20:03

Мое обходное решение:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

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

29
ответ дан 15 August 2018 в 16:53

Некоторые статистические данные, связанные с этим известным вопросом с двойной точностью.

При добавлении всех значений (a + b) с шагом 0,1 (от 0,1 до 100) мы имеем вероятность 15% точности. Обратите внимание, что ошибка может привести к несколько большим или меньшим значениям. Вот несколько примеров:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

При вычитании всех значений ( a + b , где a> b), используя шаг 0,1 (от 100 до 0,1), имеем ~ 15% вероятность ошибки точности . Вот несколько примеров:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* 15% и 34% действительно огромны, поэтому всегда используйте BigDecimal, когда точность имеет большое значение. С 2 десятичными цифрами (шаг 0,01) ситуация немного ухудшается (18% и 36%).

22
ответ дан 15 August 2018 в 16:53

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

Если компьютер работал на базе 10, 0.1 будет 1 x 10⁻¹, 0.2 будет 2 x 10⁻¹, а 0.3 будет 3 x 10⁻¹. Целочисленная математика проста и точна, поэтому добавление 0.1 + 0.2, очевидно, приведет к 0.3.

Компьютеры обычно не работают в базе 10, они работают в базе 2. Вы все равно можете получить точные результаты для некоторые значения, например 0.5, равны 1 x 2⁻¹, а 0.25 - 1 x 2⁻², а их добавление приводит к 3 x 2⁻² или 0.75. Точно.

Проблема связана с числами, которые могут быть представлены точно в базе 10, но не в базе 2. Эти числа должны быть округлены до их ближайшего эквивалента. Предполагая, что для 64-битного формата с плавающей точкой IEEE используется очень общий формат, ближайшим номером к 0.1 является 3602879701896397 x 2⁻⁵⁵, а ближайшим номером к 0.2 является 7205759403792794 x 2⁻⁵⁵; добавление их результатов в 10808639105689191 x 2⁻⁵⁵ или точное десятичное значение 0.3000000000000000444089209850062616169452667236328125. Числа с плавающей запятой, как правило, округлены для отображения.

48
ответ дан 15 August 2018 в 16:53
  • 1
    @Mark Спасибо за это ясное объяснение, но тогда возникает вопрос, почему 0.1 + 0.4 точно добавляет до 0,5 (по крайней мере, в Python 3). Также лучший способ проверить равенство при использовании float в Python 3? – pchegoor 20 January 2018 в 07:15
  • 2
    @ user2417881 Операции с плавающей запятой IEEE имеют правила округления для каждой операции, и иногда округление может дать точный ответ, даже если два числа немного отключены. Детали слишком длинны для комментария, и я все равно не эксперт в них. Как вы видите в этом ответе 0.5, это один из немногих десятичных знаков, которые могут быть представлены в двоичном формате, но это просто совпадение. Для проверки равенства см. [D0] stackoverflow.com/questions/5595425/… . – Mark Ransom 20 January 2018 в 08:35
  • 3
    @ user2417881 ваш вопрос заинтриговал меня, поэтому я включил его в полный вопрос и ответ: stackoverflow.com/q/48374522/5987 – Mark Ransom 22 January 2018 в 08:27

Чтобы предложить лучшее решение, я могу сказать, что обнаружил следующий метод:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

Позвольте мне объяснить, почему это лучшее решение. Как упоминалось выше в других ответах, рекомендуется использовать готовые для использования функции Javascript toFixed () для решения проблемы. Но, скорее всего, вы столкнетесь с некоторыми проблемами.

Представьте, что вы собираетесь добавить два числа с плавающей запятой, такие как 0.2 и 0.7, вот оно: 0.2 + 0.7 = 0.8999999999999999.

Ваш ожидаемый результат 0.9 означает, что в этом случае вам нужен результат с точностью до 1 цифры. Поэтому вы должны были использовать (0.2 + 0.7).tofixed(1), но вы не можете просто указать определенный параметр toFixed (), поскольку он зависит от заданного числа, например

`0.22 + 0.7 = 0.9199999999999999`

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

Вы могли бы сказать, что пусть это будет 10 в каждой ситуации:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

Черт! Что вы собираетесь делать с этими нежелательными нулями после 9? Пришло время преобразовать его в float, чтобы сделать это по своему желанию:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

Теперь, когда вы нашли решение, лучше предложить его как функцию:

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

Вы можете использовать его следующим образом:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9
2
ответ дан 15 August 2018 в 16:53

Просто для удовольствия я играл с представлением поплавков, следуя определениям из стандарта C99, и я написал код ниже.

Код печатает двоичное представление поплавков в 3 отдельных группах

SIGN EXPONENT FRACTION

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

Поэтому, когда вы пишете float x = 999... компилятор преобразует это число в битовое представление, напечатанное функцией xx, так что сумма, напечатанная функцией yy, будет равна заданному числу.

В действительности эта сумма является только приближение. Для числа 999,999,999 компилятор будет вставлять в бит представление float число 1,000,000,000

После кода я присоединяю консольный сеанс, в котором я вычисляю сумму терминов для обеих констант (минус PI и 999999999) что действительно существует в аппаратном обеспечении, вставленном там компилятором.

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

Вот сеанс консоли, в котором я вычисляю реальное значение float, которое существует в аппаратном обеспечении. Я использовал bc для печати суммы терминов, выводимых основной программой. Можно вставить эту сумму в python repl или что-то подобное.

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

Вот и все. Фактически значение 999999999

999999999.999999446351872

Вы также можете проверить bc, что -3.14 также возмущен. Не забудьте установить коэффициент scale в bc.

Отображаемая сумма - это то, что внутри аппаратного обеспечения. Значение, которое вы получаете, вычисляя его, зависит от установленного вами масштаба. Я установил коэффициент scale равным 15. Математически, с бесконечной точностью, кажется, что это 1 000 000 000.

40
ответ дан 15 August 2018 в 16:53

Sine Python 3.5 вы можете использовать функцию math.isclose(), если условия

import math

if math.isclose(0.1 + 0.2, 0.3, abs_tol=0.01):
    pass
0
ответ дан 15 August 2018 в 16:53

Другой способ взглянуть на это: Используются 64 бита для представления чисел. Как следствие, не может быть представлено более 2 ** 64 = 18 446 744 073 709 551 616 различных чисел.

Тем не менее, Math говорит, что существует уже бесконечное число десятичных знаков между 0 и 1. IEE 754 определяет кодировку для эффективного использования этих 64 бит для гораздо большего количества пробелов плюс NaN и +/- Infinity, поэтому есть пробелы между точно представленными числами, заполненными числами, только приближены.

К сожалению, 0,3 сидит в промежутке.

3
ответ дан 15 August 2018 в 16:53

Многие многочисленные дубликаты этого вопроса спрашивают о влиянии округления с плавающей запятой на конкретные числа. На практике легче понять, как это работает, глядя на точные результаты вычислений, а не просто на чтение. Некоторые языки предоставляют способы сделать это - например, преобразование float или double в BigDecimal в Java.

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

Применяя его к номерам в вопросе, рассматриваемым как двойные:

0,1 преобразуется в 0,1000000000000000055511151231257827021181583404541015625,

0,2 конвертирует до 0.200000000000000011102230246251565404236316680908203125,

0,3 конвертирует в 0.29999999999999999988897769753748434595763683319091796875 и

0.30000000000000004 преобразует в 0.3000000000000000444089209850062616169452667236328125.

Добавление первых двух чисел вручную или в десятичном калькуляторе, таком как Десятичное значение для конвертера с плавающей запятой , показывает точную сумму фактических входов: 0.3000000000000000166533453693773481063544750213623046875.

Если округлить до эквивалента 0,3, ошибка округления составит 0,0000000000000000277555756156289135105907917022705078125. Округление до эквивалента 0,30000000000000004 также дает ошибку округления 0,0000000000000000277555756156289135105907917022705078125.

Возвращаясь к конвертеру с плавающей запятой, необработанный шестнадцатеричный показатель для 0.30000000000000004 равен 3fd3333333333334, который заканчивается четной цифрой и, следовательно, является правильным результатом.

12
ответ дан 15 August 2018 в 16:53
  • 1
    Человеку, чье редактирование я просто откатился: я считаю, что кодовые цитаты подходят для цитирования кода. Этот ответ, будучи нейтральным по отношению к языку, вообще не содержит цитированного кода. Числа могут использоваться в английских предложениях и не превращают их в код. – Patricia Shanahan 22 November 2017 в 20:22
  • 2
    Возможно, это , почему кто-то отформатировал ваши номера как код - не для форматирования, а для удобочитаемости. – Wai Ha Lee 12 January 2018 в 22:24
  • 3
    ... также, round до четного относится к двоичному представлению , , а не десятичному представлению. Смотрите этот или, например, этот . – Wai Ha Lee 12 January 2018 в 23:33

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

Взгляните на https://posithub.org / например, который демонстрирует тип номера, называемый posit (и его предшественник unum), который обещает предложить лучшую точность с меньшим количеством бит. Если мое понимание верное, оно также фиксирует проблемы в вопросе. Весьма интересный проект, человек, стоящий за ним, является математиком его доктором Джоном Густафсоном. Все это с открытым исходным кодом, с множеством фактических реализаций в C / C ++, Python, Julia и C # (https://hastlayer.com/arithmetics).

3
ответ дан 15 August 2018 в 16:53

Эти странные цифры появляются из-за того, что компьютеры используют двоичную (базовую 2) систему счисления, в то время как мы используем десятичную (базовую 10).

Есть большинство дробных чисел, которые невозможно точно представить в двоичном или десятичном или в обоих. Результат - округленное (но точное) число результатов.

15
ответ дан 15 August 2018 в 16:53
  • 1
    Я вообще не понимаю ваш второй абзац. – Nae 27 December 2017 в 04:19
  • 2
    @Nae, я бы перевел второй абзац как «Большинство фракций не могут быть представлены точно в десятичной или двоичной. Таким образом, большинство результатов будет округлено - хотя они будут по-прежнему быть точными по количеству бит / цифр, присущих используемому представлению. & Quot; – Steve Summit 9 March 2018 в 18:19

Вы пытались решить проблему с клейкой лентой?

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

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

У меня была такая же проблема в проекте научной симуляции в c #, и я могу сказать вам, что если вы проигнорируете эффект бабочки, он превратится в большого толстого дракона и укусит вас в a **

20
ответ дан 15 August 2018 в 16:53

Могу я просто добавить; люди всегда предполагают, что это компьютерная проблема, но если вы считаете своими руками (база 10), вы не можете получить (1/3+1/3=2/3)=true, если у вас нет бесконечности, чтобы добавить 0.333 ... в 0.333 ... так, как и с (1/10+2/10)!==3/10 в базе 2, вы усекаете ее до 0,333 + 0,333 = 0,666 и, вероятно, округлите ее до 0,667, что также будет технически неточным.

Подсчитайте в тройном, а третья не проблема, может быть, какая-то раса с 15 пальцами на каждой руке спросит, почему ваша десятичная математика была сломана ...

13
ответ дан 15 August 2018 в 16:53
  • 1
    Поскольку люди используют десятичные числа, я не вижу веских причин, по которым поплавки не представлены как десятичные по умолчанию, поэтому мы получаем точные результаты. – Ronen Festinger 19 February 2017 в 23:27
  • 2
    Люди используют многие базы, отличные от базы 10 (десятичные числа), причем бинарные - это те, которые мы используем больше всего для вычисления. «Хорошая причина» заключается в том, что вы просто не можете представлять каждую фракцию в каждой базе. – user 20 February 2017 в 12:59
  • 3
    Бинарная арифметика @RonenFestinger легко реализовать на компьютерах, потому что для этого требуется всего восемь базовых операций с цифрами: скажем, $ a $, $ b $ в $ 0,1 $, все, что вам нужно знать, это $ \ operatorname {xor} (a, b) $ и $ \ operatorname {cb} (a, b) $, где xor является исключительным, а cb - бит переноса. который равен $ 0 $ во всех случаях, за исключением случаев, когда $ a = 1 = b $, и в этом случае мы имеем один (на самом деле коммутативность всех операций сохраняет вам $ 2 $, а все, что вам нужно, - это правила $ 6 $). Десятичное расширение требует $ 10 \ times 11 $ (в десятичной нотации) случаев для хранения и $ 10 $ разных состояний для каждого бита и хранения отходов на переносе. – Oskar Limka 25 March 2018 в 09:36

Другие вопросы по тегам:

Похожие вопросы: