Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
455 views
in Technique[技术] by (71.8m points)

c# - Find the float just below a value

Say I have a float X. I want to find the largest number that is less than X and can be losslessly stored in a float.

IIRC the IEEE standard says you can do this by converting the float's bits to an int representation, subtracting one, and convert back to float.

(edit: this is true for positive numbers that are not NaN or inf. For negative numbers, you must add. See Rawling's answer for more info.)

To change between representations, I only know of C#'s (cast) operator, which truncates. That's not what I want.

Is there a way to do this in C#?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here is how you can simply turn a float into an int, change it, and then turn it back into a float:

float myFloat = 10.3f;
// Get the bytes making up the float
byte[] bytes = BitConverter.GetBytes(myFloat);
// Make an int out of them
int myInt = BitConverter.ToInt32(bytes, 0);
// Change it
myInt--;
// Get the bytes making up the int
bytes = BitConverter.GetBytes(myInt);
// Make a float out of them
myFloat = BitConverter.ToSingle(bytes, 0);
// gives 10.2999992 or so

BitConverter even has this built in for the 64-bit equivalent:

double myDouble = 10.3;
long myLong = BitConverter.DoubleToInt64Bits(myDouble);
myLong--;
myDouble = BitConverter.Int64BitsToDouble(myLong); // gives 10.2999999...

However, as Peter Ruderman points out, a simple decrement of the underlying int doesn't reliably give you the next-smallest float.

In particular, for negative numbers, you need to increment the integer in order to make the float more negative. For float zero, the next smallest int actually corresponds to NaN, so you need a special case there.

Here are a couple of functions I've knocked together that should cope with these cases in general; it also looks like it sensibly travels between large numbers and positive/negative infinity, too! I've used unsafe conversions to reduce code length, but you can stick to the byte conversions above if you wish:

static unsafe float Increment(float f)
{
    int val = *(int*)&f;
    if (f > 0)
        val++;
    else if (f < 0)
        val--;
    else if (f == 0)
        return float.Epsilon;
    return *(float*)&val;
}
static unsafe float Decrement(float f)
{
    int val = *(int*)&f;
    if (f > 0)
        val--;
    else if (f < 0)
        val++;
    else if (f == 0)
        return -float.Epsilon; // thanks to Sebastian Negraszus
    return *(float*)&val;
}

As Jeppe points out, you probably also want to

  • Start each function with if (float.IsNaN(f)) return f;, so you don't accidentally increment or decrement a NaN and give something which is a number.
  • Also consider checking the input against float.PositiveInfinity or .NegativeInfinity, since, mathematically speaking, those should probably stay constant under increment/decrement.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...