Chuyển đổi một phạm vi số sang phạm vi khác, duy trì tỷ lệ


257

Tôi đang cố gắng chuyển đổi một dãy số sang số khác, duy trì tỷ lệ. Toán học không phải là điểm mạnh của tôi.

Tôi có một tệp hình ảnh trong đó các giá trị điểm có thể nằm trong khoảng từ -16000,00 đến 16000,00 mặc dù phạm vi điển hình có thể ít hơn nhiều. Điều tôi muốn làm là nén các giá trị này vào phạm vi số nguyên 0-100, trong đó 0 là giá trị của điểm nhỏ nhất và 100 là giá trị lớn nhất. Tất cả các điểm ở giữa nên giữ một tỷ lệ tương đối mặc dù một số độ chính xác bị mất Tôi muốn làm điều này trong python nhưng ngay cả một thuật toán chung cũng đủ. Tôi muốn một thuật toán trong đó phạm vi tối thiểu / tối đa hoặc một trong hai có thể được điều chỉnh (nghĩa là phạm vi thứ hai có thể là -50 đến 800 thay vì 0 đến 100).


Cảm ơn cả hai, tôi đang đưa ra câu trả lời cho cletus vì anh ấy đã vào trước và +1 cho jerry vì đã trả lời theo dõi của tôi.
SpliFF

2
cletus thực sự xin lỗi, tôi đang đưa nó cho jerry vì anh ấy mới và cần điểm.
SpliFF

2
Này đó là chủ nghĩa tuổi tác! Heheh, j / k, không phải lo lắng. :)
cletus

7
Làm thế nào mà câu hỏi này thoát khỏi lữ đoàn đóng cửa câu hỏi stackoverflow? ;)
thanikkal

Câu trả lời:


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

Hoặc dễ đọc hơn một chút:

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

Hoặc nếu bạn muốn bảo vệ cho trường hợp phạm vi cũ là 0 ( OldMin = OldMax ):

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

Lưu ý rằng trong trường hợp này, chúng tôi buộc phải chọn một trong các giá trị phạm vi mới có thể tùy ý. Tùy thuộc vào ngữ cảnh, các lựa chọn hợp lý có thể là: NewMin( xem mẫu ) NewMaxhoặc(NewMin + NewMax) / 2


oldMax phải là 16000 hay nó có thể là giá trị cao nhất trong tập hợp điểm cũ (ví dụ: 15034.00 chẳng hạn) là sự phân biệt quan trọng?
SpliFF

5
Bạn có thể làm cho nó bất cứ điều gì bạn muốn ... hãy nhớ rằng bạn có thể nhận được kết quả lạ nếu một trong các phạm vi rất nhỏ so với phạm vi khác (không chắc chắn chính xác, nhưng nếu có hơn 1000000 yếu tố khác biệt giữa kích thước của các phạm vi, đảm bảo rằng nó thực sự hoạt động như bạn mong đợi ... hoặc tìm hiểu về tính không chính xác của dấu phẩy động)
jerryjvl

2
Xem xét mức độ phổ biến của câu trả lời này, đối với trường hợp tổng quát hơn, bạn nên xem xét khả năng OldMax == OldMin, điều đó có thể dẫn đến việc chia cho số không.
người dùng

3
Điều này thật tuyệt. Có một tên toán học cho chuyển đổi này?
Tarik

2
Nó được gọi là chuyển đổi tuyến tính, @Tarik
Rodrigo Borba

65

Đó là một chuyển đổi tuyến tính đơn giản.

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

Vì vậy, chuyển đổi 10000 trên thang điểm từ -16000 đến 16000 sang thang điểm mới từ 0 đến 100 sản lượng:

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

2
Cái này sai. Bạn cần trừ Old Min khỏi Giá trị cũ trước khi chia.
SPWorley

20

Trên thực tế có một số trường hợp câu trả lời ở trên sẽ phá vỡ. Chẳng hạn như giá trị đầu vào sai, phạm vi đầu vào sai, phạm vi đầu vào / đầu ra âm.

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

9

Có một điều kiện, khi tất cả các giá trị mà bạn đang kiểm tra đều giống nhau, trong đó mã của @ jerryjvl sẽ trả về NaN.

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

5

Tôi đã không khai thác BNF cho việc này, nhưng tài liệu Arduino có một ví dụ tuyệt vời về chức năng và nó bị hỏng. Tôi đã có thể sử dụng điều này trong Python bằng cách thêm đổi tên def vào remap (nguyên nhân bản đồ là tích hợp sẵn) và loại bỏ các kiểu phôi và dấu ngoặc nhọn (nghĩa là chỉ cần loại bỏ tất cả 'dài).

Nguyên

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;
}

Con trăn

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


2

Trong danh sách được cung cấp bởi PenguinTD, tôi không hiểu tại sao các phạm vi bị đảo ngược, nó hoạt động mà không phải đảo ngược các phạm vi. Chuyển đổi phạm vi tuyến tính dựa trên phương trình tuyến tính Y=Xm+n, trong đó mnđược lấy từ các phạm vi nhất định. Thay vì tham khảo các phạm vi như minmax, sẽ tốt hơn nếu gọi chúng là 1 và 2. Vì vậy, công thức sẽ là:

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

Khi Y=y1nào X=x1Y=y2khi nào X=x2. x1, x2, y1& y2Có thể được đưa ra bất kỳ positivehoặc negativegiá trị. Xác định biểu thức trong một macro làm cho nó hữu ích hơn, sau đó nó có thể được sử dụng với bất kỳ tên đối số nào.

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

Các floatdiễn viên sẽ đảm bảo phân chia điểm nổi trong trường hợp tất cả các đối số là integergiá trị. Tùy thuộc vào ứng dụng, có thể không cần thiết phải kiểm tra phạm vi x1=x2y1==y2.


Cảm ơn! đây là chuyển đổi C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

Đây là một số hàm Python ngắn để sao chép và dán dễ dàng, bao gồm cả hàm để chia tỷ lệ toàn bộ danh sách.

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]

Mà có thể được sử dụng như vậy:

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

[0,0, 50,0, 75,0, 100,0]

Trong trường hợp của tôi, tôi muốn mở rộng một đường cong logarit, như vậy:

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

[0,0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

Tôi đã sử dụng giải pháp này trong một vấn đề tôi đang giải quyết trong js, vì vậy tôi nghĩ rằng tôi sẽ chia sẻ bản dịch. Cảm ơn lời giải thích và giải pháp.

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;
}

cảm ơn bạn! giải pháp tuyệt vời và thiết lập như là một chức năng đã sẵn sàng để đi!
Chiến đấu với lửa

1

Biến thể C ++

Tôi thấy Giải pháp của PenguinTD rất hữu ích, vì vậy tôi đã chuyển nó sang C ++ nếu có ai cần:

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; }

1

Cổng PHP

Tìm thấy giải pháp của PenguinTD hữu ích vì vậy tôi đã chuyển nó sang PHP. Tự lo lấy thân!

/**
* =====================================
*              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;
}

1

Dưới đây là phiên bản Javascript trả về một hàm thực hiện thay đổi kích thước cho phạm vi nguồn và đích được xác định trước, giảm thiểu số lượng tính toán phải được thực hiện mỗi lần.

// 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;
    }   
}

Dưới đây là một ví dụ về việc sử dụng chức năng này để chia tỷ lệ 0-1 thành -0x80000000, 0x7FFFFFFF

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

0

Đề xuất ngắn gọn / đơn giản hóa

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

đường


1
Cái gì NewRange/OldRangeđây
Zunair

0

Cá nhân tôi sử dụng lớp trình trợ giúp hỗ trợ generic (tương thích 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) )
    }
}

0

Ví dụ này chuyển đổi vị trí hiện tại của bài hát thành phạm vi góc 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;
    }

0

Liệt kê giải thích một giải pháp lót

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

Phiên bản dài hơn

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

0

Phiên bản Java

Luôn luôn làm việc cho dù bạn cho nó ăn gì!

Tôi bỏ mọi thứ mở rộng ra để dễ học hơn. Làm tròn ở cuối, tất nhiên, là tùy chọn.

    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);

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.