2015-10-12 4 views
1

Обнаружил этот гений map Функция в библиотеке Arduino. Написал же я Delphi:Delphi - Функция отображения неожиданного вывода

procedure TForm1.Button1Click(Sender: TObject); 
function map(x, in_min, in_max, out_min, out_max: Integer): extended; 
begin 
    result := (x - in_min) * (out_max - out_min)/(in_max - in_min) + out_min; 
end; 
var 
    y, in_min, in_max, out_min, out_max: Integer; 
    i: integer; 
    m: extended; 
begin 
    in_min := 0; 
    in_max := 6406963; 
    out_min := 0; 
    out_max := 474; 
    y := 0; 

    for i := in_min to in_max do begin 
    m := map(i, in_min, in_max, out_min, out_max); 
    if round(m) <> y then begin 
     y := round(m); 
     Memo1.Lines.Add(IntToStr(i) + ' = ' + FloatToStr(m)); 
    end; 
    end; 
end; 

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

def map(x, in_min, in_max, out_min, out_max): 
    "Re-maps a number from one range to another." 
    return (x - in_min) * (out_max - out_min)/(in_max - in_min) + out_min 

if __name__ == '__main__': 
    in_min = 0 
    in_max = 6406963 
    out_min = 0 
    out_max = 474 
    y = 0 
    for i in range(in_min, in_max): 
     m = map(i, in_min, in_max, out_min, out_max) 
     if round(m) != y: 
      y = round(m) 
      print(i, ' = ', m) 

Вот отрывок из моих результатов:

DELPI       EXPECTED (Python) 
    6759 = 0,500044404813  6759 = 0.50004440481395 
1358439 = 100,500047526418  1358439 = 100.50004752641775 
2710119 = 200,500050648022  2710119 = 200.50005064802153 
4061799 = 300,500053769625  4061799 = 300.5000537696253 
4521370 = 334,500040034569  4521370 = 334.50004003456866 
4530557 = -335,179597260043  4534887 = 335.50005486218663 
5418335 = -269,499996488196  5413479 = 400.5000568912291 
6405062 = -196,499949820219  6400205 = 473.50002957719596 

Итак, почему мой код Delphi производит отрицательные числа в качестве вывода и что нужно сделать, чтобы исправить это?

+0

4530557 * 474 = -2147483278 в 32-битной арифметики (переполнение), отсюда минус. Delphi использует 32-битные целые числа, произвольные целые числа точности Python AFAIK. – kludg

ответ

5

Использование целых параметров приводит к переполнению, что объясняет отрицательные значения. Это подвыражение:

(x - in_min) * (out_max - out_min) 

содержит только целые операнды и поэтому выполняется с использованием целочисленной арифметики. И это может переполняться. Первое значение, которое производит отрицательный вывод, равно x = 4530557. Давайте копать немного глубже, работая через вычисление:

x         = 4530557 
x - in_min       = 4530557 
out_max - out_min     = 474 
(x - in_min) * (out_max - out_min) = 2147484018 

И это значение больше, чем high(Integer) и так перетекает отрицательное значение.

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

Что касается других значений, то они являются одинаковыми числами с точностью, выполняемой арифметикой. Ваш код Delphi выполняет арифметику с расширенной точностью 64 бит. Код Python с двойной точностью 53 бит.

На мой взгляд, предпочтительно избегать 64-битной расширенной точности. Он нестандартен и ограничен определенными платформами. Он доступен для 32-разрядных x86, но 64-разрядные x64-компиляторы используют SSE-модуль для плавающей запятой, и этот модуль не поддерживает расширенную 64-разрядную точность. И, самое главное, выравнивание типа данных приводит к очень плохой производительности чтения/записи.

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

Таким образом, чистый результат этой функции:

function map(const x, in_min, in_max, out_min, out_max: Double): Double; 
begin 
    result := (x - in_min) * (out_max - out_min)/(in_max - in_min) + out_min; 
end; 
-1

Используйте Double вместо Extended, и вы получите тот же результат

procedure TForm1.Button1Click(Sender: TObject); 

    function map(x, in_min, in_max, out_min, out_max: Integer): Double; 
    begin 
    result := (x - in_min) * (out_max - out_min)/(in_max - in_min) + out_min; 
    end; 
var 
    y, in_min, in_max, out_min, out_max: Integer; 
    i: Integer; 
    m: Double; 
begin 
    in_min := 0; 
    in_max := 6406963; 
    out_min := 0; 
    out_max := 474; 
    y := 0; 

    for i := in_min to in_max do 
    begin 
    m := map(i, in_min, in_max, out_min, out_max); 
    if round(m) <> y then 
    begin 
     y := round(m); 
     Memo1.Lines.Add(IntToStr(i) + ' = ' + FloatToStr(m)); 
    end; 
    end; 
end; 

EDIT: См ответ Дэвида для объяснения;

+0

Это все еще возвращает отрицательные значения, потому что Integer переполнен. –

+0

На каком входном значении? –

+1

'(x - in_min) * (out_max - out_min)' может переполнить подписанное 32-битное целое число и делает в своем тесте. –

0

Ваши целые элементы переполнены, и вы должны использовать Double.

Изменить

function map(x, in_min, in_max, out_min, out_max: Integer): extended; 

К

function map(x, in_min, in_max, out_min, out_max: Int64): double; 
+2

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

+0

Да, ваш ответ лучше. –

Смежные вопросы