开发者

How to write an std::floor function from scratch [duplicate]

开发者 https://www.devze.com 2023-04-05 05:51 出处:网络
This question already has answers here: Write your own implementation of math's floor function, C 开发者_运维问答
This question already has answers here: Write your own implementation of math's floor function, C 开发者_运维问答 (5 answers) Closed 1 year ago.

I would like to know how to write my own floor function to round a float down.

Is it possible to do this by setting the bits of a float that represent the numbers after the comma to 0?

If yes, then how can I access and modify those bits?

Thanks.


You can do bit twiddling on floating point numbers, but getting it right depends on knowing exactly what the floating point binary representation is. For most machines these days its IEEE-754, which is reasonably straight-forward. For example IEEE-754 32-bit floats have 1 sign bit, 8 exponent bits, and 23 mantissa bits, so you can use shifts and masks to extract those fields and do things with them. So doing trunc (round to integer towards 0) is pretty easy:

float trunc(float x) {
    union {
        float    f;
        uint32_t i;
    } val;
    val.f = x;
    int exponent = (val.i >> 23) & 0xff; // extract the exponent field;
    int fractional_bits = 127 + 23 - exponent;
    if (fractional_bits > 23) // abs(x) < 1.0
        return 0.0;
    if (fractional_bits > 0)
        val.i &= ~((1U << fractional_bits) - 1);
    return val.f;
}

First, we extract the exponent field, and use that to calculate how many bits after the decimal point are present in the number. If there are more than the size of the mantissa, then we just return 0. Otherwise, if there's at least 1, we mask off (clear) that many low bits. Pretty simple. We're ignoring denormal, NaN, and infinity her, but that works out ok, as they have exponents of all 0s or all 1s, which means we end up converting denorms to 0 (they get caught in the first if, along with small normal numbers), and leaving NaN/Inf unchanged.

To do a floor, you'd also need to look at the sign, and rounds negative numbers 'up' towards negative infinity.

Note that this is almost certainly slower than using dedicated floating point intructions, so this sort of thing is really only useful if you need to use floating point numbers on hardware that has no native floating point support. Or if you just want to play around and learn how these things work at a low level.


Define from scratch. And no, setting the bits of your floating point number representing the numbers after the comma to 0 will not work. If you look at IEEE-754, you will see that you basically have all your floating-point numbers in the form:

0.xyzxyzxyz 2^(abc)

So to implement flooring, you can get the xyzxyzxyz and shift left by abc+1 times. Drop the rest. I suggest you read up on the binary representation of a floating point number (link above), this should shed light on the solution I suggested.

NOTE: You also need to take care of the sign bit. And the mantissa of your number is off by 127.

Here is an example, Let's say you have the number pi: 3.14..., you want to get 3.

Pi is represented in binary as

0 10000000 10010010000111111011011

This translate to

sign = 0 ; e = 1 ; s = 110010010000111111011011

The above I get directly from Wikipedia. Since e is 1. You will want to shift left s by 1 + 1 = 2, so you get 11 => 3.


#include <iostream>
#include <iomanip>

double round(double input, double roundto) {
    return int(input / roundto) * roundto;
}

int main() {
    double pi = 3.1415926353898;
    double almostpi = round(pi, 0.0001);
    std::cout << std::setprecision(14) << pi << '\n' << std::setprecision(14) << almostpi;
}

http://ideone.com/mdqFA output:

3.1415926353898
3.1415

This will pretty much be faster than any bit twiddling you can come up with. And it works on all computers (with floats) instead of just one type.


Casting to unsigned while returning as a double does what you are seeking, but under the hood. This simple piece of code works for any POSITIVE number.

#include <iostream>

double floor(const double& num) {
    return (unsigned long long) num; 
}


This has been tested on tio.run (Try It Online) and onlinegdb.com. The function itself doesn't require any #include files, but to print out the answers, I have included stdio.h (in the tio.run and onlinegdb.com, not here). Here it is:

long double myFloor(long double x) /* Change this to your liking: long double might
                                             be float in your situation.  */
{
    long double xcopy=x<0?x*-1:x;
    unsigned int zeros=0;
    long double n=1;
    for(n=1;xcopy>n*10;n*=10,++zeros);
    for(xcopy-=n;zeros!=-1;xcopy-=n)
        if(xcopy<0)
        {
            xcopy+=n;
            n/=10;
            --zeros;
        }
    xcopy+=n;
    return x<0?(xcopy==0?x:x-(1-xcopy)):(x-xcopy);
}

This function works everywhere (pretty sure) because it just removes all of the non-decimal parts instead of trying to work with the parts of floats.

The floor of a floating point number is the biggest integer less than or equal to it. Here are a some examples:

floor(5.7)  = 5
floor(3)    = 3
floor(9.9)  = 9
floor(7.0)  = 7
floor(-7.9) = -8
floor(-5.0) = -5
floor(-3.3) = -3
floor(0)    = 0
floor(-0.0) = -0
floor(-0)   = -0

Note: this is almost an exact copy from my other answer which answered a question that was basically the same as this one.

0

精彩评论

暂无评论...
验证码 换一张
取 消