Почему эти два значения не равны? [Дубликат]

В стандартном C ++ определенно нет. В переносном смысле, вероятно, нет. Иногда в конкретной ОС. Если ничего другого, вы можете открыть свой собственный исполняемый размер и проверить заголовки исполняемого файла, чтобы увидеть, как он стекируется. [Следующая проблема, конечно, «сколько стека использовалось до этого бита кода», что может быть трудно определить).

Если вы запускаете код в отдельном потоке, многие из (нисходящих) потоковых интерфейсов позволяют указать стек (или стекизировать), например, Posix threads pthread_set_stacksize или MS _beginthread . Опять же, вы не знаете ТОЧНО, сколько места было использовано до того, как оно попадет в настоящий код потока, но это, вероятно, не огромная сумма.

Конечно, во встроенной системе (например, мобильном телефоне) стекизация обычно довольно мала, 4K, 12K или 64KB очень обычна, иногда даже намного меньше, чем в некоторых системах.

Еще одна потенциальная проблема заключается в том, что вы не можете действительно знать, сколько пространства фактически используется в стеке - вы можете измерить после факта в скомпилированной системе и, конечно, если у вас есть локальный массив стека из int array[25];, мы можем знать, что он занимает не менее 25 * sizeof(int) - но может быть заполнение, компилятор сохраняет регистры в стеке и т. д. и т. д.

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

 if (enough_stack_space_for_something)
      use_stack_based_algorithm();
 else
      use_heap_based_algorithm();

Это добавит достаточное количество дополнительных накладных расходов, а больше кода, как правило, не является хорошим планом во встроенной / мобильной системе.

Edit2: Кроме того, если выделение памяти является основной частью среды выполнения, возможно, посмотрите, почему это так, например, создание блоков объектов?

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

24 ответа

Математическая математика с плавающей запятой такова. В большинстве языков программирования он основан на стандарте 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 .

1732
ответ дан 15 revs, 12 users 35% 17 August 2018 в 11:01
поделиться
  • 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 17 August 2018 в 11:01
поделиться
12
ответ дан Andrea Corbellini 17 August 2018 в 11:01
поделиться

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

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

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

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

float x = 9.9F;

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

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

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

1
ответ дан Arkadiy 17 August 2018 в 11:01
поделиться

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 17 August 2018 в 11:01
поделиться

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

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

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

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

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

26
ответ дан bruziuz 17 August 2018 в 11:01
поделиться
227
ответ дан Chris Jester-Young 17 August 2018 в 11:01
поделиться
81
ответ дан Community 17 August 2018 в 11:01
поделиться

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

Например:

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 17 August 2018 в 11:01
поделиться
  • 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 17 August 2018 в 11:01
поделиться
  • 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 делать точную математику с ней, используя обычные методы с плавающей запятой.

360
ответ дан Joel Coehoorn 17 August 2018 в 11:01
поделиться
  • 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 17 August 2018 в 11:01
поделиться
22
ответ дан Konstantinos Chalkias 17 August 2018 в 11:01
поделиться

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

Если компьютер работал в базе 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 17 August 2018 в 11:01
поделиться
  • 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 17 August 2018 в 11:01
поделиться

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

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

import math

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

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

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

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

3
ответ дан noiv 17 August 2018 в 11:01
поделиться

Многие многочисленные дубликаты этого вопроса задают вопрос о влиянии округления с плавающей запятой на конкретные числа. На практике легче понять, как это работает, глядя на точные результаты вычислений, а не просто на чтение. Некоторые языки предоставляют способы сделать это - например, преобразование 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 17 August 2018 в 11:01
поделиться
  • 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
3
ответ дан Piedone 17 August 2018 в 11:01
поделиться

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

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

15
ответ дан Piyush S528 17 August 2018 в 11:01
поделиться
  • 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 17 August 2018 в 11:01
поделиться

Могу я просто добавить; люди всегда предполагают, что это компьютерная проблема, но если вы считаете своими руками (база 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 17 August 2018 в 11:01
поделиться
  • 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
493
ответ дан Vijay S 17 August 2018 в 11:01
поделиться
Другие вопросы по тегам:

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