Массив растущих чисел с неточным двоичным представлением - PullRequest
2 голосов
/ 18 декабря 2010

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

var incrementalArray = function(start, end, step) {
    var arr = [];
    for (var i = start; i <= end; i += step) {
        // 0.1 cannot be represented precisely in binary
        // not rounding i will cause weird numbers like 0.99999999
        if (step === 0.1) {
            arr.push(i.toFixed(1));
        } else {
            arr.push(i);
        }
    }
    return arr;
};

Есть две проблемы с этой реализацией. A) Он охватывает только случай с шагом 0,1.Нет ли многочисленных других магических чисел, которые имеют повторяющиеся двоичные представления? B) Возвращенный массив не включает конечное значение, когда приращение составляет 0,1.Тем не менее, оно включает конечное значение, когда приращение имеет иное значение, делая эту функцию непредсказуемой.

incrementalArray(0.0, 3.0, 0.1);
// [0.0, 0.1, 0.2, .... 2.8, 2.9]

incrementalArray(2,10,2);
// [2, 4, 6, 8, 10]

Как эта функция может работать для всех специальных приращений и быть предсказуемой?

Ответы [ 4 ]

2 голосов
/ 18 декабря 2010

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

1 голос
/ 17 января 2012

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

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

Простейший способ, которым я знаю для определения наименее обнаруживаемой разницы в любой произвольной точке в строке действительных чисел, - это пример (с обычными предостережениями относительно соответствия размеров типов данных) - значение не имеет значения, оно было просто случайное число:

real_32 A = 999.111f;
uint_32 B = (*(uint_32*) &A) ^ 1; //create a 1-bit difference
real_32 LDD = abs(A - *(real_32*) &B);

ПОЭТОМУ, вы можете легко использовать вариант этого механизма, чтобы определить, находятся ли два действительных числа в LDD друг от друга, и посчитать, что они «равны», так как прямое сравнение вещественных чисел проблематично.

1 голос
/ 18 декабря 2010

Я думаю, это может сработать:

var incrementalArray = function(start, end, step) {
    var arr = [];
    // convert count to an integer to avoid rounding errors
    var count = +((end - start) / step).toFixed();
    for (var j = 0; j <= count; j++) {
        var i = start + j * step;
        arr.push(i);
    }
    return arr;
};

Выход: 0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1, 1.1, 1.2000000000000001, 1.3, 1.4000000000000001, 1.5, 1.6, 1.7000000000000001, 1.8, 1.9000000000000001, 2, 2.1, 2.2, 2.3000000000000002, 2.4000000000000003, 2.5, 2.6, 2.7, 2.8000000000000002, 2.9000000000000003, 3

Если вы хотите, чтобы вывод был усечен до того же числа десятичных знаков, что и вход, вы можете использовать это:

var incrementalArray = function(start, end, step) {
    var prec = ("" + step).length - ("" + step).indexOf(".") - 1;
    var arr = [];
    // convert count to an integer to avoid rounding errors
    var count = +((end - start) / step).toFixed();
    for (var j = 0; j <= count; j++) {
        var i = start + +(j * step).toFixed(prec);
        arr.push(i);
    }
    return arr;
};

Выход: 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0

0 голосов
/ 18 декабря 2010

Как насчет масштабирования их до целых чисел, а затем обратно? ( демо )

function incrementalArray(start, end, step) {
    step += "";
    var scale = Math.pow(10, step.length - step.indexOf(".") - 1);
    start *= scale;
    end *= scale;
    step *= scale;
    var values = new Array();
    for (var x = start; x < end; x += step) {
        values.push(x / scale);
    }

    return values;   
}
...