Ubah rentang angka ke rentang lain, dengan mempertahankan rasio

257

Saya mencoba mengonversi satu rentang angka ke yang lain, dengan mempertahankan rasio. Matematika bukan kelebihan saya.

Saya memiliki file gambar di mana nilai poin dapat berkisar dari -16000.00 hingga 16000.00 meskipun rentang tipikal mungkin jauh lebih sedikit. Yang ingin saya lakukan adalah kompres nilai-nilai ini ke dalam kisaran integer 0-100, di mana 0 adalah nilai titik terkecil, dan 100 adalah nilai terbesar. Semua titik di antaranya harus menjaga rasio relatif meskipun beberapa presisi hilang Saya ingin melakukan ini dalam python tetapi bahkan algoritma umum sudah cukup. Saya lebih suka algoritma di mana rentang min / max atau salah satu dapat disesuaikan (yaitu, rentang kedua bisa -50 hingga 800 bukannya 0 hingga 100).

SPIFF
sumber
Terima kasih keduanya, saya memberikan jawaban untuk cletus karena dia masuk pertama dan +1 ke jerry karena menjawab tindak lanjut saya.
SpliFF
2
sebenarnya cletus maaf, saya memberikannya kepada jerry karena dia baru dan membutuhkan poin.
SpliFF
2
Hei itu ageism! Heheh, j / k, jangan khawatir. :)
cletus
7
Bagaimana pertanyaan ini lolos dari brigade penutup stackoverflow? ;)
thanikkal

Jawaban:

536
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin

Atau sedikit lebih mudah dibaca:

OldRange = (OldMax - OldMin)  
NewRange = (NewMax - NewMin)  
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin

Atau jika Anda ingin melindungi untuk kasus di mana rentang lama adalah 0 ( OldMin = OldMax ):

OldRange = (OldMax - OldMin)
if (OldRange == 0)
    NewValue = NewMin
else
{
    NewRange = (NewMax - NewMin)  
    NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}

Perhatikan bahwa dalam hal ini kami dipaksa untuk memilih salah satu dari nilai rentang yang mungkin baru secara sewenang-wenang. Tergantung pada konteksnya, pilihan yang masuk akal dapat berupa: NewMin( lihat sampel ), NewMaxatau(NewMin + NewMax) / 2

jerigen
sumber
apakah oldMax harus 16000 atau dapatkah itu menjadi nilai tertinggi di set titik lama (katakanlah, 15034,00, misalnya) adalah perbedaan yang penting?
SpliFF
5
Anda dapat membuatnya menjadi apa pun yang Anda inginkan ... perlu diingat bahwa Anda mungkin mendapatkan hasil yang aneh jika salah satu rentang relatif kecil dibandingkan yang lain (tidak begitu yakin, tetapi jika ada lebih dari selisih faktor 10.000.000 antara ukuran rentang, pastikan bahwa itu benar-benar berperilaku seperti yang Anda harapkan ... atau belajar tentang ketidaktepatan floating point)
jerryjvl
2
Mengingat popularitas jawaban ini, untuk kasus yang lebih umum Anda harus mempertimbangkan OldMax == OldMin kemungkinan, yang dapat menghasilkan pembagian dengan nol.
pengguna
3
Ini luar biasa. Apakah ada nama matematika untuk konversi ini?
Tarik
2
Ini disebut konversi linear, @Tarik
Rodrigo Borba
65

Itu konversi linear sederhana.

new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min

Jadi, konversi 10.000 pada skala -16000 hingga 16000 ke skala baru dari 0 hingga 100 hasil:

old_value = 10000
old_min = -16000
old_max = 16000
new_min = 0
new_max = 100

new_value = ( ( 10000 - -16000 ) / (16000 - -16000) ) * (100 - 0) + 0
          = 81.25
cletus
sumber
2
Ini salah. Anda perlu mengurangi Min Lama dari Nilai Lama sebelum pembagian.
SPWorley
20

Sebenarnya ada beberapa kasus yang jawaban di atas akan pecah. Seperti nilai input salah, rentang input salah, rentang input / output negatif.

def remap( x, oMin, oMax, nMin, nMax ):

    #range check
    if oMin == oMax:
        print "Warning: Zero input range"
        return None

    if nMin == nMax:
        print "Warning: Zero output range"
        return None

    #check reversed input range
    reverseInput = False
    oldMin = min( oMin, oMax )
    oldMax = max( oMin, oMax )
    if not oldMin == oMin:
        reverseInput = True

    #check reversed output range
    reverseOutput = False   
    newMin = min( nMin, nMax )
    newMax = max( nMin, nMax )
    if not newMin == nMin :
        reverseOutput = True

    portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
    if reverseInput:
        portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin)

    result = portion + newMin
    if reverseOutput:
        result = newMax - portion

    return result

#test cases
print remap( 25.0, 0.0, 100.0, 1.0, -1.0 ), "==", 0.5
print remap( 25.0, 100.0, -100.0, -1.0, 1.0 ), "==", -0.25
print remap( -125.0, -100.0, -200.0, 1.0, -1.0 ), "==", 0.5
print remap( -125.0, -200.0, -100.0, -1.0, 1.0 ), "==", 0.5
#even when value is out of bound
print remap( -20.0, 0.0, 100.0, 0.0, 1.0 ), "==", -0.2
PenguinTD
sumber
9

Ada suatu kondisi, ketika semua nilai yang Anda periksa adalah sama, di mana kode @ jerryjvl akan mengembalikan NaN.

if (OldMin != OldMax && NewMin != NewMax):
    return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
    return (NewMax + NewMin) / 2
Teddy Garland
sumber
5

Saya tidak menggali BNF untuk ini, tetapi dokumentasi Arduino memiliki contoh yang bagus dari fungsi dan itu rusak. Saya bisa menggunakan ini dengan Python hanya dengan menambahkan def mengubah nama menjadi remap (peta disebabkan built-in) dan menghapus tipe gips dan kurung kurawal (yaitu hanya menghapus semua 'lama').

Asli

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Python

def remap(x, in_min, in_max, out_min, out_max):
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

https://www.arduino.cc/en/reference/map

dragon788
sumber
2

Dalam daftar yang disediakan oleh PenguinTD, saya tidak mengerti mengapa rentangnya terbalik, ia bekerja tanpa harus membalikkan rentang tersebut. Konversi rentang linear didasarkan pada persamaan linear Y=Xm+n, di mana mdan nditurunkan dari rentang yang diberikan. Daripada merujuk ke rentang sebagai mindan max, akan lebih baik untuk merujuknya sebagai 1 dan 2. Jadi rumusnya adalah:

Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1

Di mana Y=y1kapan X=x1, dan Y=y2kapan X=x2. x1, x2, y1& y2Dapat diberikan setiap positiveatau negativenilai. Menentukan ekspresi dalam makro membuatnya lebih bermanfaat, kemudian dapat digunakan dengan nama argumen apa pun.

#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)

Para floatpemain akan memastikan pembagian floating point dalam kasus di mana semua argumen adalah integernilai. Tergantung pada aplikasi, mungkin tidak perlu memeriksa rentang x1=x2dan y1==y2.

Brian Plummer
sumber
Terima kasih! di sini adalah konversi C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair
2

Berikut ini beberapa fungsi Python pendek untuk kemudahan salin dan rekat Anda, termasuk fungsi untuk menskalakan seluruh daftar.

def scale_number(unscaled, to_min, to_max, from_min, from_max):
    return (to_max-to_min)*(unscaled-from_min)/(from_max-from_min)+to_min

def scale_list(l, to_min, to_max):
    return [scale_number(i, to_min, to_max, min(l), max(l)) for i in l]

Yang bisa digunakan seperti ini:

scale_list([1,3,4,5], 0, 100)

[0,0, 50.0, 75.0, 100.0]

Dalam kasus saya, saya ingin skala kurva logaritmik, seperti:

scale_list([math.log(i+1) for i in range(5)], 0, 50)

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]

Charles Clayton
sumber
1

Saya menggunakan solusi ini dalam masalah yang saya pecahkan dalam js, jadi saya pikir saya akan membagikan terjemahannya. Terima kasih atas penjelasan dan solusinya.

function remap( x, oMin, oMax, nMin, nMax ){
//range check
if (oMin == oMax){
    console.log("Warning: Zero input range");
    return None;
};

if (nMin == nMax){
    console.log("Warning: Zero output range");
    return None
}

//check reversed input range
var reverseInput = false;
oldMin = Math.min( oMin, oMax );
oldMax = Math.max( oMin, oMax );
if (oldMin != oMin){
    reverseInput = true;
}

//check reversed output range
var reverseOutput = false;  
newMin = Math.min( nMin, nMax )
newMax = Math.max( nMin, nMax )
if (newMin != nMin){
    reverseOutput = true;
};

var portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
if (reverseInput){
    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
};

var result = portion + newMin
if (reverseOutput){
    result = newMax - portion;
}

return result;
}
fonstok
sumber
Terima kasih! solusi luar biasa dan ditetapkan sebagai fungsi siap untuk pergi!
Fight Fire With Fire
1

C ++ Varian

Saya menemukan Solusi PenguinTD berguna, jadi saya porting ke C ++ jika ada yang membutuhkannya:

float remap (float x, float oMin, float oMax, float nMin, float nMax) {

//range check
if( oMin == oMax) {
    //std::cout<< "Warning: Zero input range";
    return -1;    }

if( nMin == nMax){
    //std::cout<<"Warning: Zero output range";
    return -1;        }

//check reversed input range
bool reverseInput = false;
float oldMin = min( oMin, oMax );
float oldMax = max( oMin, oMax );
if (oldMin == oMin)
    reverseInput = true;

//check reversed output range
bool reverseOutput = false;  
float newMin = min( nMin, nMax );
float newMax = max( nMin, nMax );
if (newMin == nMin)
    reverseOutput = true;

float portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin);
if (reverseInput)
    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);

float result = portion + newMin;
if (reverseOutput)
    result = newMax - portion;

return result; }
pengguna1767754
sumber
1

Port PHP

Menemukan solusi PenguinTD sangat membantu jadi saya porting ke PHP. Silahkan!

/**
* =====================================
*              Remap Range            
* =====================================
* - Convert one range to another. (including value)
*
* @param    int $intValue   The value in the old range you wish to convert
* @param    int $oMin       The minimum of the old range
* @param    int $oMax       The maximum of the old range
* @param    int $nMin       The minimum of the new range
* @param    int $nMax       The maximum of the new range
*
* @return   float $fResult  The old value converted to the new range
*/
function remapRange($intValue, $oMin, $oMax, $nMin, $nMax) {
    // Range check
    if ($oMin == $oMax) {
        echo 'Warning: Zero input range';
        return false;
    }

    if ($nMin == $nMax) {
        echo 'Warning: Zero output range';
        return false;
    }

    // Check reversed input range
    $bReverseInput = false;
    $intOldMin = min($oMin, $oMax);
    $intOldMax = max($oMin, $oMax);
    if ($intOldMin != $oMin) {
        $bReverseInput = true;
    }

    // Check reversed output range
    $bReverseOutput = false;
    $intNewMin = min($nMin, $nMax);
    $intNewMax = max($nMin, $nMax);
    if ($intNewMin != $nMin) {
        $bReverseOutput = true;
    }

    $fRatio = ($intValue - $intOldMin) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
    if ($bReverseInput) {
        $fRatio = ($intOldMax - $intValue) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
    }

    $fResult = $fRatio + $intNewMin;
    if ($bReverseOutput) {
        $fResult = $intNewMax - $fRatio;
    }

    return $fResult;
}
Neil
sumber
1

Berikut ini adalah versi Javascript yang mengembalikan fungsi yang melakukan penyetelan ulang untuk rentang sumber dan tujuan yang telah ditentukan, meminimalkan jumlah perhitungan yang harus dilakukan setiap kali.

// This function returns a function bound to the 
// min/max source & target ranges given.
// oMin, oMax = source
// nMin, nMax = dest.
function makeRangeMapper(oMin, oMax, nMin, nMax ){
    //range check
    if (oMin == oMax){
        console.log("Warning: Zero input range");
        return undefined;
    };

    if (nMin == nMax){
        console.log("Warning: Zero output range");
        return undefined
    }

    //check reversed input range
    var reverseInput = false;
    let oldMin = Math.min( oMin, oMax );
    let oldMax = Math.max( oMin, oMax );
    if (oldMin != oMin){
        reverseInput = true;
    }

    //check reversed output range
    var reverseOutput = false;  
    let newMin = Math.min( nMin, nMax )
    let newMax = Math.max( nMin, nMax )
    if (newMin != nMin){
        reverseOutput = true;
    }

    // Hot-rod the most common case.
    if (!reverseInput && !reverseOutput) {
        let dNew = newMax-newMin;
        let dOld = oldMax-oldMin;
        return (x)=>{
            return ((x-oldMin)* dNew / dOld) + newMin;
        }
    }

    return (x)=>{
        let portion;
        if (reverseInput){
            portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
        } else {
            portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
        }
        let result;
        if (reverseOutput){
            result = newMax - portion;
        } else {
            result = portion + newMin;
        }

        return result;
    }   
}

Berikut adalah contoh penggunaan fungsi ini untuk skala 0-1 menjadi -0x80000000, 0x7FFFFFFF

let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);
Jamie Fenton
sumber
0

Usulan pintas / disederhanakan

 NewRange/OldRange = Handy multiplicand or HM
 Convert OldValue in OldRange to NewValue in NewRange = 
 (OldValue - OldMin x HM) + NewMin

wayne

wayne
sumber
1
Ada apa NewRange/OldRangedisini
Zunair
0

Saya pribadi menggunakan kelas pembantu yang mendukung obat generik (kompatibel Swift 3)

struct Rescale<Type : BinaryFloatingPoint> {
    typealias RescaleDomain = (lowerBound: Type, upperBound: Type)

    var fromDomain: RescaleDomain
    var toDomain: RescaleDomain

    init(from: RescaleDomain, to: RescaleDomain) {
        self.fromDomain = from
        self.toDomain = to
    }

    func interpolate(_ x: Type ) -> Type {
        return self.toDomain.lowerBound * (1 - x) + self.toDomain.upperBound * x;
    }

    func uninterpolate(_ x: Type) -> Type {
        let b = (self.fromDomain.upperBound - self.fromDomain.lowerBound) != 0 ? self.fromDomain.upperBound - self.fromDomain.lowerBound : 1 / self.fromDomain.upperBound;
        return (x - self.fromDomain.lowerBound) / b
    }

    func rescale(_ x: Type )  -> Type {
        return interpolate( uninterpolate(x) )
    }
}
Grzegorz Krukowski
sumber
0

Contoh ini mengubah posisi lagu saat ini menjadi rentang sudut 20 - 40.

    /// <summary>
    /// This test converts Current songtime to an angle in a range. 
    /// </summary>
    [Fact]
    public void ConvertRangeTests()
    {            
       //Convert a songs time to an angle of a range 20 - 40
        var result = ConvertAndGetCurrentValueOfRange(
            TimeSpan.Zero, TimeSpan.FromMinutes(5.4),
            20, 40, 
            2.7
            );

        Assert.True(result == 30);
    }

    /// <summary>
    /// Gets the current value from the mixValue maxValue range.        
    /// </summary>
    /// <param name="startTime">Start of the song</param>
    /// <param name="duration"></param>
    /// <param name="minValue"></param>
    /// <param name="maxValue"></param>
    /// <param name="value">Current time</param>
    /// <returns></returns>
    public double ConvertAndGetCurrentValueOfRange(
                TimeSpan startTime,
                TimeSpan duration,
                double minValue,
                double maxValue,
                double value)
    {
        var timeRange = duration - startTime;
        var newRange = maxValue - minValue;
        var ratio = newRange / timeRange.TotalMinutes;
        var newValue = value * ratio;
        var currentValue= newValue + minValue;
        return currentValue;
    }
penunggang kuda1210
sumber
0

Daftar pemahaman satu solusi liner

color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]

Versi yang lebih panjang

def colour_specter(waste_amount):
color_array = []
OldRange = max(waste_amount) - min(waste_amount)
NewRange = 99
for number_value in waste_amount:
    NewValue = int((((number_value - min(waste_amount)) * NewRange) / OldRange) + 1)
    color_array.append(NewValue)
print(color_array)
return color_array
zulia
sumber
0

Versi Java

Selalu berfungsi, apa pun yang Anda berikan!

Saya membiarkan semuanya diperluas sehingga lebih mudah diikuti untuk belajar. Pembulatan di akhir, tentu saja, adalah opsional.

    private long remap(long p, long Amin, long Amax, long Bmin, long Bmax ) {

    double deltaA = Amax - Amin;
    double deltaB = Bmax - Bmin;
    double scale  = deltaB / deltaA;
    double negA   = -1 * Amin;
    double offset = (negA * scale) + Bmin;
    double q      = (p * scale) + offset;
    return Math.round(q);

}
Michael Sims
sumber