0

i update my code for this :

#include <stdio.h>
int IsRightTriangle(float x,float y,float z )
{
    
    int result;
    
    if( ((x*x)+(y*y)-(z*z)>0.999 && (x*x)+(y*y)-(z*z)<1)  || ((x*x)+(z*z)-(y*y)>0.999 && (x*x)+(z*z)-(y*y)<1) || ((y*y)+(z*z)-(x*x)>0.999 &&(y*y)+(z*z)-(x*x)<1)) {
      
        result = 1; 
        return  result ;      
    } else { 
        result =0 ;
        return result;
    }
}

-but still have the same problem with decimals -for example : Running test: IsRightTriangle(edge1=15.26, edge2=8.00, edge3=13.00) -- Failed

I'm trying to write a code that checks if a triangle is right (using decimals values).

That's my code, and the problem is that it always rounds off the float value. What can I change in it?

int IsRightTriangle(float x, float y, float z)
{
    int result;
    
    if((x*x) + (y*y) == (z*z) || (x*x) + (z*z) == (y*y) || (y*y) + (z*z) == (x*x)) {
        result = 1; 
        return result ;   
    }
    else { 
        result =0 ;
        return result;
    }
}
klutt
  • 30,332
  • 17
  • 55
  • 95
proran
  • 9
  • 2
  • Please provide real example code... A [mcve] if possible. Verify that it is compilable, that code is not. – hyde Jan 17 '23 at 12:05
  • 1
    As a solution... You need to see if values are close enough. They'll almost never be equal as you noted. What is close enough, depends. – hyde Jan 17 '23 at 12:10
  • 1
    Also good to read: https://stackoverflow.com/questions/588004/is-floating-point-math-broken – Jabberwocky Jan 17 '23 at 12:13
  • Aside: You can use the ```bool``` data type, and simply return ```true``` and ```false```. – Harith Jan 17 '23 at 12:17
  • It is possible to determine whether the sum of `x`^2 and `y`^2 is exactly `z`^2. With `x`, `y`, and `z` in the format commonly used for `float`, it would be relatively simple to do using the format commonly used for `double` with some care. But is that what you really want? How will it do you any good? Suppose `x` and `y` are numbers such as there is a rational z that satisfies the condition but it is not representable in `float`, so the test will never evaluate as true for any `z`. What good will the test do you then? – Eric Postpischil Jan 17 '23 at 12:18
  • @hyde: Re “You need to see if values are close enough”: How do you know that is what they need to do? How do you know that accepting as equal values that were computed as not equal is a behavior that conforms to the specification for how their application should behave? That will also accept as true some values that are not the sides of a right triangle, and that could violate the requirements for their application? – Eric Postpischil Jan 17 '23 at 12:20
  • 1
    There are only a certain number of bits in a float. You can imagine the problem in decimal if it's easier: `if(x + y + z == 1)` does 1/3 + 1/3 + 1/3 == 1 ? No, not according to the computer, because 1/3 is 0.3333333 with only a certain number of digits and it adds up to 0.9999999, not 1. – user253751 Jan 17 '23 at 12:21
  • To answer the question directly, I suspect the problem (determine whether the given values are exactly the sides of a right triangle) might be solvable: Do preliminary tests (e.g., ensure all values are positive) and sort them so `x` is smallest, `y` is next, and `z` is last. Then convert them to `double` `X`, `Y`, and `Z`. Then square each, making `X2`, `Y2`, and `Z2`. These squares are exact since `double` has sufficient range and precision (if the common formats are used). Then `Z2 - Y2 == X2` is true iff `x`, `y`, and `z` are exactly the sides of a right triangle. – Eric Postpischil Jan 17 '23 at 12:28
  • 1
    @EricPostpischil but that probably also isn't what proran wants because the numbers would have been already rounded prior to making them floats. – user253751 Jan 17 '23 at 12:30
  • @user253751: Which is why I asked OP questions for clarification and posted the sketch of a literal solution as a comment rather than an answer. – Eric Postpischil Jan 17 '23 at 12:31
  • 1
    If the decimal input values are entered as strings, they will be rational and can be scaled up so that the side lengths are all integer values, and then tested using integer calculations. In the trivial example with sides `0.3`, `0.4` and `0.5` only the last can be exactly represented in floating point. But by shifting the decimal place you can work with `3`, `4` and `5`. – Weather Vane Jan 17 '23 at 12:36
  • 2
    @proran I just tried your code, and it seems to work fine for me. It told me that 3,4,5 and 5,12,13 were right triangles, and that others were not. Can you say a little more about this "roundoff" problem you're having? – Steve Summit Jan 17 '23 at 12:43
  • @proran If you want the program to work for triangles like 1,1,1.4142135623730950488, that'll be harder. – Steve Summit Jan 17 '23 at 12:44
  • Edit the question to provide data showing what problem you are having. In particular, is the problem you are having that the routine is returning 1 when you want it to return 0, that it is returning 0 when you want it to return 1, or both? Is this for a homework or class assignment? Are the `float` inputs to the function exact representations of original lengths? – Eric Postpischil Jan 17 '23 at 12:46
  • Please [edit] and show a [mcve] including some actual test data along with expected vs. actual output. – Jabberwocky Jan 17 '23 at 12:48
  • i update my code , and failed again – proran Jan 17 '23 at 13:21
  • Thank you for posting updated code. Please try your program on the triangles 3,4,5 and 5,12,13. Your program shows test on the triangle 8,13,15.26, but that is not an exact right triangle, and your program correctly says it is not. – Steve Summit Jan 17 '23 at 13:28
  • 1
    @proran considering your example `edge1=15.26, edge2=8.00, edge3=13.00` we have `sqrt(233)` = `15.2643375...` which is quite different from `15.26`, so what do you expect? – Jabberwocky Jan 17 '23 at 13:43
  • @proran In the updated code, if you expect `x*x + y*y` to be near `z*z`, why would you compare `x*x + y*y - z*z` to see if it's near **1**? Seems to me you want it to be near 0, plus or minus. – Steve Summit Jan 17 '23 at 13:49
  • @jabberwocky that's the problem , how can i make my code take the whole decimal 15.2643375 and not just 15.26 – proran Jan 17 '23 at 13:51
  • 1
    @proran _"how can i make my code take the whole decimal 15.2643375 and not just 15.26 "_: provide 15.2643375 intead of 15.26 in the code that calls `IsRightTriangle`. Please [edit] and add a [mcve], that is a complete minimal C program with `main` etc. and hardcoded test data with actual output and expected output. – Jabberwocky Jan 17 '23 at 13:58
  • 2
    @proran Be aware that 15.26 is only part of the problem. The square root of 233 is not 15.26. But it is not 15.2643375, either. So you are *never* going to be able to perfectly test the triangle 8, 13, √233. A certain amount of, as you called it, roundoff is inevitable here, before you even get to your `IsRightTriangle ` function. You may choose to say that 15.2643375 is "close enough", and then you have to decide how close is "close enough" when asking whether `x*x + y*y` is equal to `z*z`. (And you're almost there.) – Steve Summit Jan 17 '23 at 14:39
  • Here is a sketch of a solution using only `float`: First, exclude cases where any of x, y, and z is not a positive finite number. Then sort them in ascending order. Scale them by a power of the floating-point base to make z near 1. Then calculate the square of z in two parts: `z1 = z*z; z0 = fmaf(z, z, -z1);`. Do the same for x and y, so we have 3 squares in 6 parts. Negate the results for x and y. Sort all six numbers by magnitude. Add them in order of descending magnitude… – Eric Postpischil Jan 17 '23 at 15:47
  • … If x, y, and z form a right triangle, this will yield exactly zero. If they do not, I think the sum must not be zero (I think that, by adding in descending order, there can be a rounding error only if the true sum is not zero, and that, if there is a rounding error, the computed sum also cannot be zero, but I would need to work out a proof for that.) – Eric Postpischil Jan 17 '23 at 15:48
  • That's an [acute triangle](https://www.wolframalpha.com/input?i=triangle+sides+15.26%2C+8.00%2C+13.00), so it would make sense that is would return false. – Neil Jan 17 '23 at 16:16
  • 1
    @EricPostpischil *"How do you know that is what they need to do?"* It's in the name of the function. Also, the idea of having floats with exact non-integer values (instead of always having some rounding errors compared to what would be exactly correct value in some of the numbers) is basically never practical, because input/output as decimal number already breaks exactness. – hyde Jan 17 '23 at 20:36
  • @hyde: No, the name of the function does not say they need to see whether the values are close enough. – Eric Postpischil Jan 17 '23 at 20:43
  • @EricPostpischil Non-integer floating point _variables_ are never exact, so "close enough" is the best they're going to get. It's clear this is not some esoteric case of bit-perfectly exact float values only. – hyde Jan 17 '23 at 22:41
  • @hyde: Re “Non-integer floating point variables are never exact”: That is false. – Eric Postpischil Jan 17 '23 at 22:46
  • @EricPostpischil make it "known to be exact". Just look at the function. It does not control its input. There is no use case for requiring exactly precise float values as input. – hyde Jan 17 '23 at 22:56

3 Answers3

1

You can't avoid rounding. I repeat: you cannot avoid it. It is not possible.

There are only a certain number of bits in a float.

Here's the decimal version of the problem if we pretend that floats are in decimal instead of binary:
When I write if(x + y + z == 1) does 1/3 + 1/3 + 1/3 == 1 ? No, not according to the computer, because 1/3 is 0.3333333 with only a certain number of digits and it adds up to 0.9999999, not 1.

It is quite common to just add some "tolerance range" as a quick workaround. Instead of if(x + y + z == 1) you might write if(x + y + z > 0.9999 && x + y + z < 1.0001) and this is often good enough, especially for computer games. How many 9's and 0's do you need? Well, just write a few and then play the game and see if it feels right.

For some applications this might not be suitable, and then you have to invent a completely different way to do whatever you are trying to do. For example, you might store all your numbers as rational numbers (numerator and denominator, a.k.a. fractions) instead of floating-point. Rational numbers can be calculated exactly - if the numerator and denominator don't overflow. C doesn't have rational numbers built in, so you would need to write your own library functions like struct rational, void rational_add(struct rational *a, struct rational *b) and so on.

user253751
  • 57,427
  • 7
  • 48
  • 90
  • 2
    The answer is wrong. Maybe if you had said “It is very difficult to avoid rounding using only `float` arithmetic,” you would have been off to a better start. But “It is not possible” is false. It is possible to do exact arithmetic with floating-point arithmetic, using sufficient care. There are textbooks written about it. And, in this case, the solution might be quite simple, using `double` arithmetic to work with the `float` inputs. But a solution is possible even using just `float`, although it would require a fair amount of work. – Eric Postpischil Jan 17 '23 at 12:33
  • 1
    @user253751 You have to be careful with the analogy about 1/3 + 1/3 + 1/3. It's a nice analogy, but don't say "not according to the computer", because if you do `1./3 + 1./3 + 1./3` on any computer I know of, you *do* get exactly `1.000`. – Steve Summit Jan 17 '23 at 12:47
  • 1
    @user253751: Computing `1.f/3 + 1.f/3 + 1.f/3` using IEEE-754 binary32 arithmetic for `float` yields exactly 1, not 1.0000000298. Computing `1./3 + 1./3 + 1./3` using IEEE-754 binary64 arithmetic for `double` also yields exactly 1. If you compute `1.f/3` using `float` arithmetic, then convert that to `double`, then do the two additions using `double`, then you get 1.0000000298023223876953125. But this question is tagged C, and C expression evaluation does not do that without some intervention by the programmer using casts or assignments to change the type. – Eric Postpischil Jan 17 '23 at 12:53
  • @user253751 if you sum the three values you'll find they are exactly equal to 1.0 without involving roundoff by `printf` to a number of decimal places. – Weather Vane Jan 17 '23 at 12:53
  • @SteveSummit TIL that adding it to itself 3 times *just coincidentally* rounds the opposite way during the addition. – user253751 Jan 17 '23 at 12:54
  • @user253751 Coincidence or not, it's a fact that floating-point arithmetic often *does* work precisely. That's why it's a disservice to new learners (and also to pioneers such as Goldberg and Kahan) to make it sound like floating point is always imprecise, or that avoiding rounding is not possible. It's true that floating-point values are *often* (and inherently) imprecise, and that a well-chosen approximate-equality test is *often* necessary. But floating-point arithmetic is actually impressively precise, and you can use it much better if you appreciate this. – Steve Summit Jan 17 '23 at 13:00
  • @user253751 TIL that TIL stands for "Today I Learned". :-) – Steve Summit Jan 17 '23 at 13:13
  • @Zakk: It takes time to craft good floating-point code, and what solution to use depends on the problem requirements. Is `double` arithmetic available? Then converting to `double` to do the arithmetic is fairly simple. Is an efficient `fmaf` available? Then that may be useful. If not, the arithmetic could be done in `float` using Dekker splits and other techniques. Can we assume IEEE-754 binary32 for `float`? If so, compile-time characteristics can be used. So I need more information and time to construct a proper answer. – Eric Postpischil Jan 17 '23 at 14:13
  • 1
    @EricPostpischil `double` might be able to accurately represent the result of any float*float multiplication (floats which have already been rounded, mind you, so it's too late) but if that's your objection, just imagine the original question had `double` inputs and the question remains. – user253751 Jan 17 '23 at 14:14
  • @user253751: No, I will not imagine criteria. – Eric Postpischil Jan 17 '23 at 14:15
  • @EricPostpischil consider the question says "how can I avoid rounding off values in C?" and not "how can I avoid rounding a float*float multiplication in C?" – user253751 Jan 17 '23 at 14:15
  • @user253751: So what about the title? One way to avoid rounding errors is to use a more precise type. The proper procedure here is for OP to clarify the question, not for you to imagine criteria that are not stated. – Eric Postpischil Jan 17 '23 at 14:16
  • So it is clear that when it comes to floating point in C, there is no single approach for all cases. It may be possible to get the OP to specify a long list of assumptions that enable us to give a solution to that exact problem (though I doubt the OP has the necessary insight), but how useful would that be to other people looking up this question? I am upvoting this answer for providing a useful angle and conveying the fact that it may not be applicable to all situations. In most problems I have seen in real applications, tolerance or conversion to integer arithmetics has been the solution. – nielsen Jan 17 '23 at 14:29
  • 1
    @EricPostpischil Can you give some good references that discuss that in more details? I've never thought it was possible. – Zakk Jan 17 '23 at 14:35
  • The reason Eric's being a stickler here is that fuzzy answers lead to fuzzy thinking. Yet floating-point arithmetic is *not* (or need not be) inherently fuzzy. And I can speak from personal experience: Despite imagining myself to be a C and programming expert, for years (no, decades) I knew — or thought I knew — that floating-point arithmetic was just inherently imprecise, inherently imperfect, that it was always going to give you results that were slightly off, for the most baffling reasons. – Steve Summit Jan 17 '23 at 14:55
  • But the truth is that IEEE-754 floating-point arithmetic is very well defined, it's incredibly precise, and it really does give you very good — nay, excellent — results the vast majority of the time... *as long as you appreciate what "excellent" is.* But you'll never appreciate that if all you've ever heard is fuzzy and only approximately-true assertions such as that "You can't avoid rounding. It is not possible." (Don't get me wrong: most of user253751's answer is fine, but it contains a few significant inaccuracies.) – Steve Summit Jan 17 '23 at 14:55
  • Now, it's a fair question: Is a questioner like the OP here ready to appreciate all of these subtleties today, or will he be satisfied with a half-hearted and basically wrong (or at least significantly misleading) answer like "Just use `if(fabs x*x + y*y - z*z) < 0.001`"? That's a different question, but remember, whatever the OP might be satisfied with, we're not putting answers here just for the OP, they're supposed to stand the test of time and be useful for anyone who comes along later. – Steve Summit Jan 17 '23 at 14:55
  • 1
    @user253751: Re “You should write your own answer instead of arguing about everyone else's”: No, that is wrong; writing an answer should not stand in the stead of pointing out and explaining deficiencies and errors in other answers. The two activities are independent. As I have written previously, it takes time to craft good floating-point code. I may write an answer depending on how OP’s statement of the problem evolves. That answer will take as long as it takes, and there is no reason bad answers should go uncriticized in the interim. – Eric Postpischil Jan 17 '23 at 15:11
  • @Zakk: Re “Can you give some good references that discuss that in more details?”: See [Handbook of Floating-Point Arithmetic by Jean-Michel Muller *et al*](https://smile.amazon.com/Handbook-Floating-Point-Arithmetic-Jean-Michel-Muller/dp/3319765256/ref=sr_1_1?crid=1XLS47VJU663D&keywords=floating-point+arithmetic&qid=1673968347&sprefix=floating-point+arithmetic%2Caps%2C159&sr=8-1&ufe=app_do%3Aamzn1.fos.18ed3cb5-28d5-4975-8bc7-93deae8f9840). – Eric Postpischil Jan 17 '23 at 15:14
  • The question of "What's the best way to test for 'close enough' floating-point equality?" turns out to be quite a hard one, and I suspect the definitive answer has still not been written. You could do worse than [here](https://c-faq.com/fp/fpequal.html), but there's more to be said. See [here](https://stackoverflow.com/questions/13940316#13941383) for some more good information on the problem — but the bottom line is that there's no, one right tolerance value; it depends quite a bit on the problem at hand. – Steve Summit Jan 17 '23 at 15:14
1

There are two issues here, and the answer to neither of them is that you want to try to somehow avoid doing any rounding. In fact, you're probably going to need to do some well-chosen rounding.

The first issue is that no finite-precision floating-point representation can represent every fraction exactly. It's especially true that it's impossible to represent irrational numbers like sqrt(233) exactly.

You tried to test your program on a triangle with sides 8, 13, and the square root of 233. Mathematically, that's a perfect right triangle, but it's impossible to ask your program to test that right triangle, because you literally can't say "the square root of 233" when you ask it. You certainly can't say 155.26. You can try 15.26434, but that's inaccurate, as is 15.264337522473748, or 15.2643375224737480252559487. No finite representation is ever going to be perfectly accurate.

And then the second problem is that the inherent imprecision in the representation of most fractions means that you're rarely going to find that, say, x*x + y*y is exactly equal to z*z. This is an example of a comparison for floating-point equality that you probably don't want to try to make. (You will often hear it said that you should "never compare floating-point numbers for equality", which is not too bad as a rule of thumb, but there's a lot more to be said.)

You tried updating your comparison code to

if(x*x + y*y - z*z > 0.999 && x*x + y*y - z*z < 1 || … )

but that's not quite right, either. If the quantities x*x + y*y and z*z are almost equal, their difference will be close to 0, although it could end up on either side. So what you were trying to do is more like

if(x*x + y*y - z*z > -.001 && x*x + y*y - z*z < 0.001 || … )

and this might actually work, to a point. You could simplify it (avoid the repeated subexpression) by writing

if(fabs(x*x + y*y - z*z) < 0.001 || … )

Using a fixed accuracy threshold like 0.001 like this doesn't work particularly well, however. Some better, relative approaches can be found in this question's other answers, or in question 14.5 in the C FAQ list.

Also, as another rule of thumb, you should almost never use type float. You should almost always use type double, which has roughly twice the precision, and will tend to give you far fewer headaches.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
0

To compare floating point values, you should compare equality with a given tolerance. Unless the scale of the numbers are known, it is important to apply the tolerance to a "normalized" expression of the difference between the numbers.

For example, fabs(a-b) < tolerance only gives the desired result if a and b are close to 1. A better alternative is fabs(a/b)-1.0, however, that assumes that b is not 0 or very close to 0. In this case, I assume that the operands are not zero (or close to zero), and thus a solution is as follows:

#include <stdbool.h>
#include <math.h>

bool IsRightTriangle( float x, float y, float  z)
 {
    float tolerance = 0.0001;
    int result;
    
    return (fabs((x*x + y*y) / (z*z) - 1.0) < tolerance ||
        fabs((x*x + z*z) / (y*y) - 1.0) < tolerance ||
        fabs((y*y + z*z) / (x*x) - 1.0) < tolerance);
}
nielsen
  • 5,641
  • 10
  • 27
  • 1
    This is not what the question requests, and it is not good general advice for working with floating-point arithmetic. While this might eliminate false negatives, where the function incorrectly reports that values that do form a right triangle do not form a right triangle, it increases false positives, where the function incorrectly reports that values that do not form a right triangle do form a right triangle. It is not always desired to make this trade-off, which is one reason this is not good general advice. – Eric Postpischil Jan 17 '23 at 12:21
  • i update my code for this :#include int IsRightTriangle(float x,float y,float z ) { int result; if( ((x*x)+(y*y)-(z*z)>0.999 && (x*x)+(y*y)-(z*z)<1) || ((x*x)+(z*z)-(y*y)>0.999 && (x*x)+(z*z)-(y*y)<1) || ((y*y)+(z*z)-(x*x)>0.999 &&(y*y)+(z*z)-(x*x)<1)){ result = 1; return result ; } else { result =0 ; return result; } } and still have a problem , for example : Running test: IsRightTriangle(edge1=15.26, edge2=8.00, edge3=13.00) -- Failed – proran Jan 17 '23 at 13:16
  • my code failed agian with : IsRightTriangle(edge1=35.61, edge2=22.00, edge3=28.00) -- Failed – proran Jan 17 '23 at 13:22
  • @proran Do you expect these examples to give "true" or "false" as result? With a tolerance of `0.0001` the first gives false and the second gives true on my system. They are both not very close to being exact triangles. Finding the right tolerance for the application may not be easy. – nielsen Jan 17 '23 at 13:30
  • @proran By the way, compared to your updated code, notice that I divide the values to be compared (to mitigate scaling issues) and take the absolute value of the difference (to allow for tolerance in both directions). – nielsen Jan 17 '23 at 13:35