Saya ingin menjepit nilai x
ke kisaran [a, b]
:
x = (x < a) ? a : ((x > b) ? b : x);
Ini cukup mendasar. Tapi saya tidak melihat fungsi "penjepit" di perpustakaan kelas - setidaknya tidak di System.Math
.
(Untuk orang yang tidak sadar untuk "menjepit" nilai adalah memastikan bahwa nilainya berada di antara beberapa nilai maksimum dan minimum. Jika lebih besar dari nilai maks, maka diganti dengan nilai maks, dll.)
Jawaban:
Anda bisa menulis metode ekstensi:
public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T> { if (val.CompareTo(min) < 0) return min; else if(val.CompareTo(max) > 0) return max; else return val; }
Metode ekstensi masuk dalam kelas statis - karena ini adalah fungsi tingkat rendah, mungkin harus masuk ke beberapa ruang nama inti dalam proyek Anda. Anda kemudian dapat menggunakan metode dalam file kode apa pun yang berisi direktif penggunaan untuk namespace misalnya
using Core.ExtensionMethods int i = 4.Clamp(1, 3);
.NET Core 2.0
Dimulai dengan .NET Core 2.0
System.Math
sekarang memilikiClamp
metode yang dapat digunakan sebagai gantinya:using System; int i = Math.Clamp(4, 1, 3);
sumber
IComparable
adalah tidak ada tinju yang terjadi. Ini seharusnya berjalan sangat cepat. Ingatlah bahwa dengandouble
danfloat
,CompareTo
metode tersebut sesuai dengan urutan totalNaN
yang kurang dari semua nilai lainnya, termasukNegativeInfinity
. Jadi tidak setara dengan<
operator. Jika Anda menggunakan<
dengan tipe floating-point, Anda harus mempertimbangkan bagaimana memperlakukanNaN
juga. Ini tidak relevan untuk tipe numerik lainnya.NaN
dalam kedua kasus tersebut. Versi dengan<
dan>
akan mengeluarkanNaN
dan menggunakanNaN
untukmin
ataumax
akan secara efektif membuat penjepit satu sisi. DenganCompareTo
itu akan selalu kembaliNaN
jikamax
adaNaN
.Cukup gunakan
Math.Min
danMath.Max
:sumber
int a0 = x > a ? x : a; return a0 < b ? a0 : b
mana (meskipun memberikan hasil yang benar) tidak terlalu ideal.Math.Min(Math.Max(x, min), max)
menghasilkan satu perbandingan lebih dari yang diperlukan jika x <min.Mencoba:
public static int Clamp(int value, int min, int max) { return (value < min) ? min : (value > max) ? max : value; }
sumber
Tidak ada, tapi tidak terlalu sulit untuk membuatnya. Saya menemukan satu di sini: penjepit
Ini:
public static T Clamp<T>(T value, T max, T min) where T : System.IComparable<T> { T result = value; if (value.CompareTo(max) > 0) result = max; if (value.CompareTo(min) < 0) result = min; return result; }
Dan itu bisa digunakan seperti:
int i = Clamp(12, 10, 0); -> i == 10 double d = Clamp(4.5, 10.0, 0.0); -> d == 4.5
sumber
Clamp(T value, T min, T max)
Tidak ada satu pun di
System.Math
namespace .Ada
MathHelper
Kelas yang tersedia untuk studio game XNA jika itu yang Anda lakukan:sumber
Hanya membagikan solusi Lee dengan masalah dan kekhawatiran komentar yang ditangani, jika memungkinkan:
public static T Clamped<T>(this T value, T min, T max) where T : IComparable<T> { if (value == null) throw new ArgumentNullException(nameof(value), "is null."); if (min == null) throw new ArgumentNullException(nameof(min), "is null."); if (max == null) throw new ArgumentNullException(nameof(max), "is null."); //If min <= max, clamp if (min.CompareTo(max) <= 0) return value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value; //If min > max, clamp on swapped min and max return value.CompareTo(max) < 0 ? max : value.CompareTo(min) > 0 ? min : value; }
Perbedaan:
ed
) untuk (selanjutnya) menunjukkan bahwa nilai tidak dijepit di tempat, dan sebagai gantinya, nilai baru dikembalikan (Lihat komentar @ JimBalter ).null check
pada semua masukan (Lihat komentar @ JeppeStigNielsen ).min
danmax
jikamin > max
(Lihat komentar @ JeppeStigNielsen ).Keterbatasan: Tidak ada klem satu sisi. Jika
max
adaNaN
, selalu kembaliNaN
(Lihat komentar Herman ).sumber
nameof
tidak bekerja untuk C # 5 atau lebih rendah.Menggunakan jawaban sebelumnya, saya memadatkannya ke kode di bawah ini untuk kebutuhan saya. Ini juga akan memungkinkan Anda untuk menjepit angka hanya dengan min atau maks.
public static class IComparableExtensions { public static T Clamped<T>(this T value, T min, T max) where T : IComparable<T> { return value.CompareTo(min) < 0 ? min : value.ClampedMaximum(max); } public static T ClampedMinimum<T>(this T value, T min) where T : IComparable<T> { return value.CompareTo(min) < 0 ? min : value; } public static T ClampedMaximum<T>(this T value, T max) where T : IComparable<T> { return value.CompareTo(max) > 0 ? max : value; } }
sumber
return value.ClampedMinimum(min).ClampedMaximum(max);
?Kode di bawah ini mendukung penentuan batas dalam urutan apapun (yaitu
bound1 <= bound2
, ataubound2 <= bound1
). Saya telah menemukan ini berguna untuk nilai penjepitan yang dihitung dari persamaan linier (y=mx+b
) di mana kemiringan garis dapat meningkat atau menurun.Saya tahu: Kode ini terdiri dari lima operator ekspresi bersyarat yang sangat jelek . Masalahnya, itu berhasil , dan tes di bawah ini membuktikannya. Jangan ragu untuk menambahkan tanda kurung yang tidak perlu jika Anda menginginkannya.
Anda dapat dengan mudah membuat kelebihan lain untuk tipe numerik lain dan pada dasarnya menyalin / menempel tes.
Peringatan: Membandingkan bilangan floating point tidaklah mudah. Kode ini tidak menerapkan
double
perbandingan dengan baik. Gunakan pustaka perbandingan titik mengambang untuk menggantikan penggunaan operator perbandingan.public static class MathExtensions { public static double Clamp(this double value, double bound1, double bound2) { return bound1 <= bound2 ? value <= bound1 ? bound1 : value >= bound2 ? bound2 : value : value <= bound2 ? bound2 : value >= bound1 ? bound1 : value; } }
xUnit / FluentAssertions tes:
public class MathExtensionsTests { [Theory] [InlineData(0, 0, 0, 0)] [InlineData(0, 0, 2, 0)] [InlineData(-1, 0, 2, 0)] [InlineData(1, 0, 2, 1)] [InlineData(2, 0, 2, 2)] [InlineData(3, 0, 2, 2)] [InlineData(0, 2, 0, 0)] [InlineData(-1, 2, 0, 0)] [InlineData(1, 2, 0, 1)] [InlineData(2, 2, 0, 2)] [InlineData(3, 2, 0, 2)] public void MustClamp(double value, double bound1, double bound2, double expectedValue) { value.Clamp(bound1, bound2).Should().Be(expectedValue); } }
sumber
Jika saya ingin memvalidasi rentang argumen dalam [min, max], saya menggunakan kelas praktis berikut:
public class RangeLimit<T> where T : IComparable<T> { public T Min { get; } public T Max { get; } public RangeLimit(T min, T max) { if (min.CompareTo(max) > 0) throw new InvalidOperationException("invalid range"); Min = min; Max = max; } public void Validate(T param) { if (param.CompareTo(Min) < 0 || param.CompareTo(Max) > 0) throw new InvalidOperationException("invalid argument"); } public T Clamp(T param) => param.CompareTo(Min) < 0 ? Min : param.CompareTo(Max) > 0 ? Max : param; }
Kelas bekerja untuk semua objek yang
IComparable
. Saya membuat instance dengan rentang tertentu:RangeLimit<int> range = new RangeLimit<int>(0, 100);
Saya juga memvalidasi argumen
range.Validate(value);
atau jepit argumen ke kisaran:
var v = range.Validate(value);
sumber