numpy.sum на массиве 1D не возвращает точное число [duplicate]

В .NET Core я играл со всеми верхними ответами - но без каких-либо успехов. Я много изменил структуру БД и каждый раз добавлял новую попытку миграции в update-database, но получал ту же ошибку.

Затем я начал remove-migration один за другим, пока Консоль диспетчера пакетов бросил мне исключение:

Миграция '20170827183131 _ ***' уже была применена к базе данных

. После этого я добавил новую (add-migration) и update-database успешно

Таким образом, мое предложение было бы: очистить все ваши временные миграции до вашего текущего состояния БД.

2368
задан Rann Lifshitz 26 May 2018 в 11:59
поделиться

27 ответов

Математическая математика с плавающей запятой такова. В большинстве языков программирования он основан на стандарте IEEE 754 . JavaScript использует 64-битное представление с плавающей запятой, которое совпадает с Java double. Суть проблемы состоит в том, что числа представлены в этом формате как целое число раз в два раза; рациональные числа (такие как 0.1, который является 1/10), знаменатель которого не является степенью двух, не могут быть точно представлены.

Для 0.1 в стандартном формате binary64 представление может записывается в точности как

  • 0.1000000000000000055511151231257827021181583404541015625 в десятичной форме или
  • 0x1.999999999999ap-4 в нотации C99 hexfloat .

Напротив, рациональное число 0.1, которое является 1/10, может быть записано точно как

  • 0.1 в десятичной форме или
  • 0x1.99999999999999...p-4 в аналоге обозначения гексафлоата C99, где ... представляет собой бесконечную последовательность 9.

Константы 0.2 и 0.3 в вашей программе также будут приближенными к их истинные ценности. Бывает, что ближайший double до 0.2 больше, чем рациональное число 0.2, но ближайший double до 0.3 меньше, чем рациональное число 0.3. Сумма 0.1 и 0.2 заканчивается выше, чем рациональное число 0.3 и, следовательно, не согласуется с константой в вашем коде.

Достаточно полное рассмотрение арифметических вопросов с плавающей запятой Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой . Для более простого объяснения см. floating-point-gui.de .

1731
ответ дан 15 revs, 12 users 35% 15 August 2018 в 22:58
поделиться
  • 1
    «Некоторая константа ошибки», также известная как значение Epsilon. – Gary Willoughby 9 April 2010 в 13:47
  • 2
    Я думаю, что "некоторая константа ошибки" является более правильным, чем «Эпсилон» потому что нет «Эпсилон», которые могут быть использованы во всех случаях. Различные эпсилоны должны использоваться в разных ситуациях. И машина epsilon почти никогда не является хорошей константой для использования. – Rotsor 5 September 2010 в 00:33
  • 3
    Это не довольно true, что вся математика с плавающей запятой основана на стандарте IEEE [754]. Есть еще некоторые системы, которые используют старый IBM шестнадцатеричный FP, например, и все еще есть графические карты, которые не поддерживают арифметику IEEE-754. Однако это справедливо для разумного приближения. – Stephen Canon 4 January 2013 в 01:36
  • 4
    Cray откалибровал соответствие IEEE-754 для скорости. Java также ослабила свою приверженность как оптимизацию. – Art Taylor 12 February 2013 в 05:12
  • 5
    Я думаю, вы должны добавить что-то к этому ответу о том, как всегда должны вычисляться деньги, всегда выполняться с арифметикой с фиксированной точкой на целых числах , потому что деньги квантуются. (Возможно, имеет смысл делать внутренние расчеты по бухгалтерскому учету в крошечных долях процента или независимо от вашей наименьшей единицы валюты - это часто помогает, например, уменьшить ошибку округления при конвертировании «29,99 долл. США в месяц» в суточный тариф, но это все равно должна быть арифметикой с фиксированной точкой.) – zwol 12 May 2014 в 23:23

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

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

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

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

9
ответ дан agc 15 August 2018 в 22:58
поделиться

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

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

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

12
ответ дан Andrea Corbellini 15 August 2018 в 22:58
поделиться

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

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

x в вопросе - это переменная float.

Одним из примеров может быть

float x = 9.9F;

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

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

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

1
ответ дан Arkadiy 15 August 2018 в 22:58
поделиться

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 вместо Math использовать ошибки плавания

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

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

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

0
ответ дан bortunac 15 August 2018 в 22:58
поделиться

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

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

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

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

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

26
ответ дан bruziuz 15 August 2018 в 22:58
поделиться

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

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

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

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


Большинство опытных программистов, конечно же, знают реальный ответ, который заключается в том, что нет возможности собрать кусок точной десятой или пятой пиццы используя эти срезы, независимо от того, насколько мелко вы их нарезаете. Вы можете сделать довольно хорошее приближение, и если вы добавите аппроксимацию 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.

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

<ч>

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

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

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

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

Резюме

Арифметика с плавающей точкой точный, к сожалению, он не очень хорошо сочетается с нашим обычным представлением числа base-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.

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

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

& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; x / (2n + 5n).

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

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

Итак, в десятичной форме мы не можем представлять 1/3. Поскольку база 10 включает в себя 2 как простой коэффициент, каждое число, которое мы можем записать как двоичную дробь , также может быть записано в виде базовой дроби. Однако вряд ли что-либо, что мы пишем как 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/3. Но в двоичном коде мы не можем делать 1/10 или 1/3.

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

Работа с ним

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

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

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

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

Заключение

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

81
ответ дан Community 15 August 2018 в 22:58
поделиться
  • 1
    Мой ответ был отклонен вскоре после его публикации. С тех пор я сделал много изменений (включая явное указание повторяющихся битов при записи 0,1 и 0,2 в двоичном формате, которые я пропустил в оригинале). В случае непредвиденного того, что наблюдатель видит это, не могли бы вы дать мне несколько отзывов, чтобы я мог улучшить свой ответ? Я чувствую, что мой ответ добавляет что-то новое, поскольку обработка суммы в IEEE 754 не рассматривается таким же образом в других ответах. Хотя «Что должен знать каждый компьютерный ученый ...» охватывает один и тот же материал, мой ответ называет конкретно случаем 0,1 + 0,2. – Wai Ha Lee 24 February 2015 в 08:29
  • 2
    Округление до ближайшего целого не является безопасным способом решения проблемы сравнения во всех случаях. 0.4999998 и 0.500001 округляют до разных целых чисел, поэтому существует «опасная зона», вокруг каждой граничной точки. (Я знаю, что эти десятичные строки, вероятно, не точно представлены как бинарные поплавки IEEE.) – Peter Cordes 9 December 2016 в 04:31
  • 3
    Кроме того, несмотря на то, что с плавающей точкой является «наследием», формат, он очень хорошо разработан. Я не знаю ничего, что кто-то изменил бы, если бы перепроектировал его сейчас. Чем больше я узнаю об этом, тем больше я думаю, что это действительно хорошо . например смещенный показатель означает, что последовательные двоичные поплавки имеют последовательные целочисленные представления, поэтому вы можете реализовать nextafter() с целым приращением или декрементом в двоичном представлении поплавка IEEE. Кроме того, вы можете сравнить float как целые числа и получить правильный ответ, за исключением случаев, когда они оба отрицательные (из-за знака-величины по сравнению с дополнением 2). – Peter Cordes 9 December 2016 в 04:35
  • 4
    Я не согласен, поплавки должны храниться как десятичные, а не двоичные, и все проблемы решаются. – Ronen Festinger 19 February 2017 в 20:32
  • 5
    Не должно быть « x / (2 ^ n + 5 ^ n) " быть " x / (2 ^ n * 5 ^ n) "? – Wai Ha Lee 5 February 2018 в 08:34
  • 6
    @RonenFestinger - примерно 1/3? – Stephen C 15 August 2018 в 03: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 Дуглас Крокфорд: JavaScript: Хорошие детали: Приложение A - Ужасные части (стр. 105) .

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

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

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

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

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

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

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

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

29
ответ дан Justineo 15 August 2018 в 22:58
поделиться

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

При добавлении всех значений ( 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), мы имеем вероятность 34% точности. Вот несколько примеров:

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
ответ дан Konstantinos Chalkias 15 August 2018 в 22:58
поделиться

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

Если компьютер работал в базе 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
ответ дан Mark Ransom 15 August 2018 в 22:58
поделиться
  • 1
    @Mark Спасибо за это ясное объяснение, но тогда возникает вопрос, почему 0.1 + 0.4 точно добавляет до 0,5 (по крайней мере, в Python 3). Также лучший способ проверить равенство при использовании float в Python 3? – pchegoor 20 January 2018 в 04:15
  • 2
    @ user2417881 Операции с плавающей запятой IEEE имеют правила округления для каждой операции, и иногда округление может дать точный ответ, даже если два числа немного отключены. Детали слишком длинны для комментария, и я все равно не эксперт в них. Как вы видите в этом ответе 0.5, это один из немногих десятичных знаков, которые могут быть представлены в двоичном формате, но это просто совпадение. Для проверки равенства см. stackoverflow.com/questions/5595425/… . – Mark Ransom 20 January 2018 в 05:35
  • 3
    @ user2417881 ваш вопрос заинтриговал меня, поэтому я превратил его в полный вопрос и ответ: stackoverflow.com/q/48374522/5987 – Mark Ransom 22 January 2018 в 05: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
ответ дан Mohammad lm71 15 August 2018 в 22:58
поделиться

Просто для удовольствия я играл с представлением поплавков, следуя определениям из стандарта 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
ответ дан Nae 15 August 2018 в 22:58
поделиться

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

import math

if math.isclose(0.1 + 0.2, 0.3, abs_tol=0.01):
    pass
0
ответ дан nauer 15 August 2018 в 22:58
поделиться

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

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

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

3
ответ дан noiv 15 August 2018 в 22:58
поделиться

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

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

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

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

0,2 преобразуется в 0.200000000000000011102230246251565404236316680908203125,

0,3 конвертируется в 0,29999999999999999989897769753748434595763683319091796875 и

0,30000000000000004 преобразуется в 0,3000000000000000444089209850062616169452667236328125.

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

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

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

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

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

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

3
ответ дан Piedone 15 August 2018 в 22:58
поделиться

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

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

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

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

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

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

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

20
ответ дан sonne 15 August 2018 в 22:58
поделиться

Могу я просто добавить; люди всегда предполагают, что это компьютерная проблема, но если вы считаете своими руками (база 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,677, что также будет технически неточным.

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

13
ответ дан user 15 August 2018 в 22:58
поделиться
  • 1
    Поскольку люди используют десятичные числа, я не вижу веских причин, по которым поплавки не представлены как десятичные по умолчанию, поэтому мы получаем точные результаты. – Ronen Festinger 19 February 2017 в 20:27
  • 2
    Люди используют многие базы, отличные от базы 10 (десятичные числа), причем бинарные - это те, которые мы используем больше всего для вычисления. «Хорошая причина» заключается в том, что вы просто не можете представлять каждую фракцию в каждой базе. – user 20 February 2017 в 09: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 в 06:36
492
ответ дан Vijay S 15 August 2018 в 22:58
поделиться
82
ответ дан Community 5 September 2018 в 22:46
поделиться
41
ответ дан Nae 5 September 2018 в 22:46
поделиться
1
ответ дан ashleedawg 29 October 2018 в 05:43
поделиться
Другие вопросы по тегам:

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