3

Suppose to have n (integer) contiguous segments of length l (floating point). That is:

Segment 0 = [0, l)
Segment 1 = [l, 2*l)
Segment 2 = [2*l, 3*l)
... 
Segment (n-1) = [(n-1)*l, n*l) 

Given a number x (floating point) I want to determine the id of the segment it lies inside.

My first idea is the following:

int segmentId = (int) floor(x/l);

Anyway, this sometimes does not work. For example, consider

double l = 1.1;
double x = 5.5;
int segmentId = (int) floor(x/l); //returns 5


double l = 1.1;
double x = 6.6;
int segmentId = (int) floor(x/l); //returns 5!!!

Of course, due to finite arithmetic, this does not work well. Maybe some extra checks are required in order to have a robust implementation, but I really don't know how to proceed further.

The question is: how would you solve the problem "In which segment a given number lies in?"

the_candyman
  • 1,463
  • 3
  • 20
  • 35
  • 5
    It's purely a floating-point issue. Instead of 6, you get something like 5.999999 and then you floor it and voila. – Armen Tsirunyan Jun 29 '16 at 10:18
  • 1
    @JanDvorak I'm aware of the numerical issues. This question is not about a generic numerical issue, but it is on a specific problem as the title says "In which segment a given number lie in?" – the_candyman Jun 29 '16 at 10:19
  • 1
    You just have to accept that floating point math is not exact enough to give you the correct answer in this case. – Sven Nilsson Jun 29 '16 at 10:20
  • 1
    *Maybe some extra checks are required in order to have a robust implementation* -- Maybe your problem is much bigger than finding which segment a number lies in. What if that value is computed (instead of a constant, like your example shows), and the computation itself has round-off errors? The problem starts way before you get to finding which segment a number is in. Possibly you should use a custom type that has more precision than `double`. – PaulMcKenzie Jun 29 '16 at 10:31
  • Can `l` be any float number or only ones of quite low number of decimal places? If so I would go for fixed point numbers. – MrSmith42 Jun 29 '16 at 10:42
  • Since your method seems to also rely on the boundaries (after all you declared them by using incl./excl. notation), in my opinion using floating point numbers to represent them is contradictory, since they are not exact at boundary calculations. You might want to consider using `decimal` instead. – philkark Jun 29 '16 at 11:04
  • 1
    @the_candyman: It is in fact a duplicate. The original question doesn't assign a specific meaning to numbers, it applies to all numbers regardless of the specific meaning. In your case, the meaning of a number is a position on a segment, but that doesn't change math. – MSalters Jun 29 '16 at 13:55

5 Answers5

7

Your problem is that neither 1.1, nor 6.6 are representable exactly in binary floating point. So when you type

double l = 1.1;
double x = 6.6;

you get 2 numbers stored in l and in x, which are slightly different than 1.1 and 6.6. After that, int segmentId = (int) floor(x/l); determines the correct segment for those slightly different numbers, but not for the original numbers.

You can solve this problem by using a decimal floating point data type instead of binary. You can check C++ decimal data types and Exact decimal datatype for C++? for the libraries, or implement the decimal data type yourself.

But still the problem will remain for numbers, which are not representable in finite decimal floating point, such as 1/3 (circulating fraction), sqrt(2) (irrational), pi (transcendental), etc.

Community
  • 1
  • 1
Serge Rogatch
  • 11,119
  • 4
  • 58
  • 117
2

Just in case u don't specifically want an O(1) answer you can go for the O(logn) answer by just doing a binary search on the segments.

Shervin Sorouri
  • 200
  • 1
  • 8
  • This way no division,(except the usual mid division) is needed. – Shervin Sorouri Jun 29 '16 at 10:51
  • This will not work either, because the numbers become different right after storing in binary floating point variables. See my answer for details. – Serge Rogatch Jun 29 '16 at 10:54
  • @SergeRogatch : Idk but ill code the binary search version and ill see if it makes any difference. – Shervin Sorouri Jun 29 '16 at 10:58
  • @SergeRogatch this way works because you just multiply to get the boundaries which is correct to 1ULP. You don't divide and discard the fractional part so there's no lost of precision – phuclv Jun 29 '16 at 11:00
  • 2
    @shervin, there is no need to code O(logN) algorithm, because it would be enough to multiply by the 3 adjacent indices: `segmentId-1`, `segmentId` and `segmentId+1`, maybe even 2 of them can be proved. – Serge Rogatch Jun 29 '16 at 11:07
  • 1
    @LưuVĩnhPhúc, the numbers `1.1` and `6.6` become slightly different right after storing them in variables. How can you get precise result after this? – Serge Rogatch Jun 29 '16 at 11:08
  • 1
    @SergeRogatch: Anyway, me being so stubborn, i codded it, you were right, theres even a simple if which can prove ur point: `if(6.6 != 6*1.1) cout << "GG\n";` – Shervin Sorouri Jun 29 '16 at 11:10
  • @SergeRogatch we don't care about 6.6. We're checking if the double value that is closest to 6.6 is within some segment begins with a double value and end with another double value, i.e. `1.1*(segment-1) <= x <= 1.1*segment` – phuclv Jun 29 '16 at 11:26
  • 1
    @LưuVĩnhPhúc : The problem here is if x is on the borders of the segment, specifically the lower bound then when u feed x to your program it will be slightly less than that lower bound which will result in an incorrect answer giving u the previous segment. – Shervin Sorouri Jun 29 '16 at 11:34
  • @Lưu Vĩnh Phúc, I understood that the OP wants the results for exactly 1.1 and 6.6, not for the values closest to them in binary floating point. The result can be different. – Serge Rogatch Jun 29 '16 at 11:48
  • @SergeRogatch: If you can rely on IEEE754, then the rounding is well-defined. The results are precise to the last bit, it's just that the number of bits is limited. – MSalters Jun 29 '16 at 14:04
  • @SergeRogatch yeah if the exact 6.6 decimal is needed then binary floating-point can't help – phuclv Jun 29 '16 at 14:50
  • @MSalters, and because of that last bit the number can drop into 1 of 2 segments: the correct one (mathematically) or the wrong one. – Serge Rogatch Jun 29 '16 at 18:41
1

What precision does your solution require? There can always be a problem with marginal values for given segment, cause they are most likely unrepresentable. I think adding a very small epsilon in this case could help. However it may fail in other case.

michauzo
  • 346
  • 1
  • 9
1

how would you solve the problem "In which segment a given number lie in?"

You should divide the number by the segment length, then truncate the fractional part away. Like this:

int segmentId = (int) floor(x/l);

It seems that you have already figured this out.

Of course, due to finite arithmetic, this does not work well.

If the result of 6.6 / 1.1 happens to be5.9999999999999991118215802998747676610946655273438, then 5 is in fact the correct segment for the result.

If you would like 6.6 / 1.1 to be exactly 6, then your problem is with finite precision division, which doesn't do what you want and with finite precision representation of floating point numbers that has no exact representation for all numbers. The segmentation itself worked perfectly.

I really don't know how to proceed further

Either don't use finite precision floating point (use fixed or arbitrary precision), or don't require the results of calculations to be exact.

eerorika
  • 181,943
  • 10
  • 144
  • 256
1

Check the segments again after the division.

bool inSegment(double x, double l, double segment)
{
    return (x >= l*(segment-1)) && (x < l*segment);
}

int segmentId;
double segment = floor(x/l);

if (inSegment(x, l, segment-1))
    segmentId = segment - 1;
else if (inSegment(x, l, segment))
    segmentId = segment;
else if (inSegment(x, l, segment+1))
    segmentId = segment + 1;
else
    printf("Something wrong happened\n");

Or use an epsilon and round the value up if the value is close enough to an integer above.

phuclv
  • 27,258
  • 11
  • 104
  • 360
  • 2
    With this program, if `x = 5.5; l = 1.1`, then the segmentId will be 6. This isn't any better than what OP already has. Epsilon won't help either. – eerorika Jun 29 '16 at 11:04