4
\$\begingroup\$

I have a method that transforms any input uint into a [min,max] set. I am looking for improvements on the function, specifically to get rid of the input == uint.MaxValue case.

public static uint NumberInRange(uint input, uint min, uint max)
{
 decimal divider = uint.MaxValue / (decimal)max;
 if (input == uint.MaxValue)
 return (uint)max;
 var i = (uint)(Math.Floor(input / divider) + min);
 return i;
}

Let me know if you see any ways to improve this function, or if you think that there is any subtle bug within it.

I am basically just looking into a way to map a bigger set [0,uint.MaxValue] into a smaller set [min,map].

Let's say the inputs are:

NumberInRange(0, 10, 100) - I should get 10

NumberInRange(uint.MaxValue, 10, 100) - I should get 100

For any number in between, I want them to be mapped "equally" between members of the set.

Let's say I put in a number that gives value between 0 and 1:

for (input / (uint.MaxValue/max)))

All 0.xxxx values should map to min (instead of getting min for numbers between 0 and 0.5, and min+1 for 0.5-1.5, etc) in order to see equal distribution in mapping (hence Math.Floor).

I think the function does it's job pretty well. I was just looking to see if I can eliminate that if (input == uint.MaxValue) case since it's a weird edge case (for which result is max+1). This is also one thing that can be nitpicked from math standpoint - all members of the set will get mapped from n elements, while the last member will get mapped from n+1 elements (because of this special if case).

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 29, 2014 at 13:29
\$\endgroup\$
5
  • \$\begingroup\$ you should write some edge-case unit-tests but I don't think that you need the part where you check vor max-Value at all \$\endgroup\$ Commented Jun 29, 2014 at 14:48
  • 6
    \$\begingroup\$ I would like some more clarity on what the code is supposed to do. For example, what should NumberInRange(1000, 10, 100) return? (and why!). I feel like there is a bug, and that for the divider, the code should be ... = uint.MaxValue / (decimal)(max - min) \$\endgroup\$ Commented Jun 29, 2014 at 17:25
  • 1
    \$\begingroup\$ Code does not behave properly when min and max are close to each other and significantly greater than zero. Perhaps the input labeled max is supposed to represent the interval size? \$\endgroup\$ Commented Jun 30, 2014 at 19:51
  • \$\begingroup\$ @Snowbody no, I specifically need it to work in [min,max] set (not interval size). If you can provide me with exact values for input,min,max that it doesn't work for you, I can test and try to fix it. \$\endgroup\$ Commented Jun 30, 2014 at 22:18
  • 1
    \$\begingroup\$ using your existing code, input = uint.maxValue/2, min=100005, max=100006 should result in either 100005 or 100006 depending on your conventions, but it doesn't. It gets 3000017. \$\endgroup\$ Commented Jun 30, 2014 at 22:26

3 Answers 3

3
\$\begingroup\$

In C-style languages, it's much more natural to specify an integer range half-open. Think about a for loop. The range is specified by the lowest value in the range, and the lowest value that is not in the range, aka one more than the highest value in the range. This is to avoid the off-by-one error you're experiencing. (Failure to follow this convention causes no end of trouble to people trying to write binary search routines) So I'd rewrite your code as follows:

public static uint NumberInRange(uint input, uint min, uint maxPlusOne)
{
 if (input == uint.MaxValue)
 throw new ArgumentOutOfRangeException("input");
 decimal divider = uint.MaxValue / (decimal)(maxPlusOne-min);
 var i = (uint)(Math.Floor(input / divider) + min);
 return i;
}

Yes, the code is exactly the same (except I put the missing -min into the denominator of divider), just how you call it is different. For one, uint.MaxValue is not allowed. Instead of mapping [0,uint.MaxValue] to [min,max] it maps [0,uint.MaxValue) to [min,maxPlusOne). Note that both intervals are half-open.

answered Jun 30, 2014 at 14:51
\$\endgroup\$
3
\$\begingroup\$

I would recommend considering this using more explicitly the Two-Point Equation of a Line. Your code is basically doing the same thing, but thinking more explicitly in terms of the underlying mathematics would help avoid errors or extra complication.

Here is how I would implement this:

public static uint LinearTransform(uint x, uint x1, uint y1, uint x2, uint y2) {
 ulong a = (ulong)(y2 - y1)*(x - x1);
 uint b = (x2 - x1);
 return (uint)(a/b) + (a%b*2>= b)?1:0 + y2;
}

I don't actually know C#, so it is likely that the code is not syntactically correct, or breaks the style rules of the language, but it should get the point across.

The (a%b*2 >= b)?1:0 term is to compensate for the way integer division always truncates to zero. This will make it instead round up if the fractional part would be 1/2 or more, so we don't need to do any floating point computations.

This function can then be used to perform your original task by calling it as LinearTransform(input, 0, min, uint.MaxValue, max).

answered Jun 29, 2014 at 21:01
\$\endgroup\$
3
  • \$\begingroup\$ The line uint a = (y2 - y1)*(x - x1); is subject to integer overflow, which is what the OP was trying to avoid. \$\endgroup\$ Commented Jun 30, 2014 at 14:43
  • \$\begingroup\$ @Snowbody I have fixed the problem by using a ulong to store that product. \$\endgroup\$ Commented Jul 1, 2014 at 19:54
  • \$\begingroup\$ yep, that works \$\endgroup\$ Commented Jul 1, 2014 at 19:55
2
\$\begingroup\$

This code implicitly types the i variable. Why? You already know that i will be a uint type. Microsoft recommends against this.

The use of var does have at least the potential to make your code more difficult to understand for other developers. For that reason, the C# documentation generally uses var only when it is required.

Also, i is a very poor variable name.

These are both moot points though, because that variable is entirely superfluous. That part of the function can be rewritten to directly return the value of the expression.

return (uint)(Math.Floor(input / divider) + min);
answered Jun 29, 2014 at 14:03
\$\endgroup\$
10
  • 2
    \$\begingroup\$ I don't know if you actually don't know what var does, or if you're just using confusing terminology, but C# var is very different from variant in other languages. \$\endgroup\$ Commented Jun 29, 2014 at 18:12
  • \$\begingroup\$ I fail to see how I'm confused @svick. var implicitly types and it seems unnecessary when i is known to be a uint. \$\endgroup\$ Commented Jun 29, 2014 at 18:45
  • 1
    \$\begingroup\$ Yes, but calling var variant is confusing for those who know the term variant from other languages (like VB 6). And I've never seen it called this way. \$\endgroup\$ Commented Jun 29, 2014 at 20:19
  • 3
    \$\begingroup\$ No! That's not what var means at all. It means "compiler, figure out what this type is at compile time, so that I don't have to spell it out". \$\endgroup\$ Commented Jun 29, 2014 at 23:38
  • 1
    \$\begingroup\$ @ckuhn203 do you mean that var is ill advised? I actually thought it was recommended practice when the type of the variable being created is obvious at creation i.e. var i = new TestClass() see msdn.microsoft.com/en-us/library/ff926074.aspx \$\endgroup\$ Commented Jun 30, 2014 at 7:35

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.