dormky 4 Posted Friday at 10:45 AM Consider : procedure Test(); var number: single; begin number := 0.96493138416; // Too long for a single precision // Assert(number = 0.96493138416); // Will fail, normal number := RoundTo(number, -3); Assert(number = 0.965); Fails end; Here number is supposed to be a value I'm getting from the data I'm running the test on. Due to binary representation the number will never be exactly correct, however trying to Assert with the value given by the debugger doesn't work either. So how am i meant to assert on floats ? Thanks. Share this post Link to post
Cristian Peța 117 Posted Friday at 11:14 AM Maybe using SameValue(number, 0.965, 0.001)? Or if you need all the precision the test binary. Share this post Link to post
Anders Melander 2007 Posted Friday at 11:16 AM Multiply by a 10^"number of decimals", Trunc to convert to integer, Assert on the integer value 1 Share this post Link to post
David Heffernan 2442 Posted Friday at 11:59 AM It depends a bit on what your goals are. But if you want to test equality then you need to put the expected value into a single. That's hard to do in a compiler because delphi literal syntax doesn't allow to specify the type of a floating point literal. You could declare a typed constant of single type and compare against that. Share this post Link to post
dormky 4 Posted Friday at 12:36 PM Ended up landing on this, works fine for my purpose : procedure AssertFloat(const value, should: Extended); begin Assert(Abs(value - should) < 0.00001); end; 1 Share this post Link to post
dummzeuch 1644 Posted Friday at 04:11 PM (edited) What's wrong with Assert(SameValue(Value, Should)) Or, if you want to set the maximum allowed difference yourself Assert(SameValue(Value, Should, MaxDelta)) Edit: @Cristian Peța already suggested that. Edited Friday at 04:13 PM by dummzeuch Share this post Link to post
David Heffernan 2442 Posted Friday at 05:03 PM Every time someone calls SameValue a puppy dies 3 1 Share this post Link to post
dummzeuch 1644 Posted Saturday at 08:22 AM 15 hours ago, David Heffernan said: Every time someone calls SameValue a puppy dies OK, I bite: What's the problem with SameValue? Share this post Link to post
Rollo62 581 Posted Saturday at 08:57 AM 21 hours ago, Anders Melander said: Multiply by a 10^"number of decimals", Trunc to convert to integer, Assert on the integer value I do the same in many places, even keeping Integers for example by the Factor 10000 stored somewhere in the first place, but then you must ensure that it never happens that there is an unexpected overflow of the Integer. Thats why this solution always leaves me with a bad feeling, when I used it, ... but there's no such thing as a free lunch Share this post Link to post
Brandon Staggs 382 Posted Monday at 04:48 PM On 6/6/2025 at 12:03 PM, David Heffernan said: Every time someone calls SameValue a puppy dies Why is it lethal to puppies? Share this post Link to post
Kas Ob. 138 Posted yesterday at 07:03 AM 14 hours ago, Brandon Staggs said: Why is it lethal to puppies? Look at these cute puppies 0.96493138416 , look at them pawsitively ! SameValue will murder some of them as its error tolerance is fixed (not adaptive) and the comparison is .. meh .. it looks close (aka very far error tolerance), so some cute puppies and kittens will be missed by the world 😭 Share this post Link to post
Anders Melander 2007 Posted yesterday at 07:48 AM 40 minutes ago, Kas Ob. said: SameValue will murder some of them as its error tolerance is fixed Only if you don't specify a tolerance. I can't see anything wrong with it if you specify a tolerance. Share this post Link to post
Kas Ob. 138 Posted yesterday at 08:39 AM 38 minutes ago, Anders Melander said: Only if you don't specify a tolerance. I can't see anything wrong with it if you specify a tolerance. Right, yet the formula for the comparison should be MaxValue := Max(Abs(A), Abs(B)); if (Abs(A - B) / MaxValue) < Epsilon // Epsilon is the optional provided one or Machine Epsilon and the machine Epsilon should be For singles = 1.19e-07 and for doubles = 2.22e-16 These numbers are known and defined https://en.wikipedia.org/wiki/Machine_epsilon#Values_for_standard_hardware_arithmetics and not what Math unit define in the RTL as approximation to these, then for some unexplained reason multiplied by 1000 reducing precision 3 degree to kill 3 puppies at least 😭 Share this post Link to post
Anders Melander 2007 Posted yesterday at 08:57 AM 5 minutes ago, Kas Ob. said: the formula for the comparison should be Should? As far as I can tell the current implementation matches the documented behavior. Yours doesn't. You might prefer another behavior, which is perfectly reasonable, but it doesn't make the current one wrong. I agree that SameValue without the Epsilon parameter is at best problematic but, with regard to the choice of default Epsilon, we don't know what the criteria was for the values they chose (because it isn't documented) so I can't see how we can say that they are wrong. Again; We might prefer other values but that doesn't make the current values wrong. Share this post Link to post
Kas Ob. 138 Posted yesterday at 09:30 AM 17 minutes ago, Anders Melander said: Should? As far as I can tell the current implementation matches the documented behavior. Yours doesn't. You might prefer another behavior, which is perfectly reasonable, but it doesn't make the current one wrong. The right way to do (the formula) at that very Wikipedia page, from https://en.wikipedia.org/wiki/Machine_epsilon#Relationship_to_absolute_relative_error the first line has the formula, and its proof, while the last line explain how to generalize it to Quote Although this proof is limited to positive numbers and round-by-chop, the same method can be used to prove the inequality in relation to negative numbers and round-to-nearest machine representations. Notice that the mentioned formula i suggested is the exact one but it use the max between x and y, unlike the Wikipedia listed formula, the reason is using the biggest one will increase the precision as the left side might fall beyond one machine epsilon step, using the biggest (max) hence it is the right way to do it, also the SameValue should be check the same value within best highest precision possible, so while we have defined and mathematically proven formula we should stick to the most standardized references, in other words it should utilize division instead of multiplication. This formula is not from yesterday... Digging more and consulting with Wikipedia sources i see what might be the origin of these numbers used in Math unit here from 1999 https://people.eecs.berkeley.edu/~demmel/cs267/lecture21/lecture21.html Quote Floating point formats Name of format Bytes to Number of bits Macheps Number of Approx range store in significand exponent bits -------------- -------- -------------- ------- ------------- ------------ Single 4 24 2^(-24) ~ 1e-7 8 10^(+-38) Double 8 53 2^(-53) ~ 1e-16 11 >=10^(+-308) Double extended >=10 >=64 <=2^(-64) ~ 1e-19 >=15 >=10^(+-4932) Quadruple 16 113 2^(-113)~ 1e-34 15 10^(+-4932) While the correct epsilon values should be For singles = 1.19e-07 and for doubles = 2.22e-16 Now comes to the formula from that page again we have Quote Example 4: Condition Estimation To compute an error bound for the solution of a linear system of equations Ax=b, we need an estimate of the condition number of A, ||A||*||inv(A)||, where ||.|| is some matrix norm. One may then approximately bound the error in the computed solution x by || computed x - true x || ------------------------- <= ||A|| * ||inv(A)|| * macheps || computed x || Share this post Link to post
Rollo62 581 Posted yesterday at 10:00 AM 2 hours ago, Kas Ob. said: ... as its error tolerance is fixed (not adaptive) ... I think always in tolerances, when it comes to real numbers, because thats what the real numbers were all about, IMHO. Perhaps, I'm a little biased by handling with measurement technology too much ... Not sure what you mean by "adaptive" here, but what comes to my thought is statistics then, which directly follows the real numbers tolerances. Share this post Link to post
Anders Melander 2007 Posted yesterday at 10:17 AM 33 minutes ago, Kas Ob. said: The right way ... I think you are missing my point. I'm saying that SameValue, with Epsilon specified, is documented to work in the way it does now. If it didn't then that would be a bug. You can argue that it would be better if it worked in another way (e.g. Epsilon relative to the magnitude of value) but that is not how it is documented to work, it is also subjective, and it depends on how one intends to use it. It's like arguing that the right way to index strings is zero based. Share this post Link to post
Kas Ob. 138 Posted yesterday at 10:18 AM 10 minutes ago, Rollo62 said: I think always in tolerances, when it comes to real numbers, because thats what the real numbers were all about, IMHO. Perhaps, I'm a little biased by handling with measurement technology too much ... Not sure what you mean by "adaptive" here, but what comes to my thought is statistics then, which directly follows the real numbers tolerances. Well this is longer discussion and need scratching behind the ear, not from the puppies fleas but pretending thinking deeply The suggested formula above is the best one or lets say the most right one, but it is not adaptive with fixed error tolerance, adaptive one will be more complex and depend on the exponent part and scale with it, think about it, comparing numbers in billions makes the machine Epsilon very silly, so such epsilon (error tolerance) should be magnified a little to accommodate the fact these numbers and the need for their comparison is coming for arithmetic operations, so multiple mE by 10^n makes more sense. Share this post Link to post
Kas Ob. 138 Posted yesterday at 10:29 AM 1 minute ago, Anders Melander said: I think you are missing my point. I'm saying that SameValue, with Epsilon specified, is documented to work in the way it does now. If it didn't then that would be a bug. May be i missed, SameValue does work as documented, no word there. Yet it doesn't use the standardized value for the default Epsilon and it does that without documentation, its documentation should declare it use low(reduced) precision for comparison by default. Share this post Link to post
Anders Melander 2007 Posted yesterday at 10:33 AM 1 minute ago, Kas Ob. said: its documentation should declare it use low(reduced) precision for comparison by default. No argument there. Share this post Link to post
Brandon Staggs 382 Posted 22 hours ago 22 hours ago, Brandon Staggs said: Why is it lethal to puppies? Good information in this thread. @David HeffernanI was actually interested in your reasons for not using SameValue and what you use as an alternative. Share this post Link to post
Rollo62 581 Posted 20 hours ago (edited) 6 hours ago, Kas Ob. said: ... adaptive one will be more complex and depend on the exponent part and scale with it, think about it, comparing numbers in billions makes the machine Epsilon very silly, so such epsilon (error tolerance) should be magnified a little to accommodate ... Yes, I completely get that point. Only I'm unsure in which cases this would really make sense. Usually I know my workroom, where I expect my variable to sit in. If you don't know that and you really want to switch tolerances, then I'm not really sure why. That would mean a 1^3 will get the tolerance of 1.0, a 1^6 might get a tolerance of 1000.0, for example. So what happens when you multiply two number, do you really want to re-calculate the tolerances too? Ok, in more complex calculations it should be enough to calculate the tolerance at the very end, according to the result value. Instead of putting such overhead to an "adaptive" functionality, I would rather put more overhead into statistics, to achive mean and std deviation, which are much more useful to me. I must confess, that I did such "adaptive" tolerance too, in some projects, but when using statistics for getting more insights of data, this was always much more useful to me. Nevertheless, maybe there are good reasons for adaptive tolerances too, only its already late today and I cannot find a real use case at the moment. 🤔 The only two reasons I could see for now, are for display clearance of the last digits, or if you want to convert the real numbers into integers then afterwards, to keep their last digits clean . Edited 20 hours ago by Rollo62 Share this post Link to post
Anders Melander 2007 Posted 17 hours ago I would argue that the type of Epsilon depends on the use case; For one an absolute explicit Epsilon is suitable (SameValue(Value, Epsilon)), for another an absolute implicit Epsilon will make sense (SameValue(Value) but with some better defaults), and for yet another a relative magnitude Epsilon would be desirable (let's call it KasObSameValue(Value, Epsilon) since we don't have it in the RTL). FWIW, the same discussion could be had about IsZero. I mostly use it to avoid division by zero and overflow errors caused by division by a very small number. I'm not really comfortable doing it but the alternative is the gazillion sporadic exceptions we had in the old application I'm working on, before we began using it. Share this post Link to post
Kas Ob. 138 Posted 4 hours ago 12 hours ago, Rollo62 said: Usually I know my workroom, where I expect my variable to sit in. If you don't know that and you really want to switch tolerances, then I'm not really sure why. Can't agree more, yet there is many cases when you can't predict the usage of a library or the user input, in real estate 1 square meter could be priced at $1000 or even $10000 even for small office at 33 square meter, so the fractal part worth non negligible value, in other cases the asset we need to process all the same is at million square meter, here the price could be not a problem but are the result have the same precision ? i know i sound like losing the subject !, Please bare with me a little, i will try to explain few things which many knows and many doesn't know, just to reach some points at the end. i will start with single float point, it is 32bit and we all know its structure and how it work, but i am writing about the things that are not very known ! lets look at the smallest number that single can have, and again i am talking about positive only here as the negative while they are less but ,, meh ,, it is just signal bit, anyway the 3 smallest numbers, and here pay attention i am going with normal and not subnormal ones, these are smaller a lot but they are different case, while both belongs to IEEE 754, the smallest one is 2^−126, i don't have calculator to get the decimal value because it will help here, so i am using online Wolfram Alpha 2^-126 = 1.1754943508222875079687365372222456778186655567720875215087517062784172594547271728515625 × 10^-38 but we are limited in precision and i need the decimal representation and singles has 24 precision so 2^-126 = 0.000000000000000000000011754943508222875079687365372222456778186655567720875215087517062784172594547271728515625 But this approximation and could have more puppies, i mean digits, yet we (computers and software) can't handle such precision out of the box so lets truncate it a little, we get the smallest three singles 0 0.000000000000000000000011754943508222875 | 0.000000000000000000000011754943782280733 | the step is 0.000000000000000000000000000273057858 again in these numbers i am taking normal not subnormal, the difference exist in the 1 on the left side and it affect precision, long story and irrelevant to this post. While the biggest numbers are 340282313029632462711731687303715884032.0 | 340282346638528859811704183484516925440.0 | the difference aka step here is 33408896097170127936.0 Infinity (overflow) the step plays crucial rule in float point, as it resize based on the exponent used, in other words we might add billions and yet the result will not change a bit ! Fun fact on side note, ULP is the official name for these adaptive steps and let see what happen when and where ULP is >= 1 ULP(x) = 2^(exp2(x)-23) , so for >=1 we have exp2(x) >= 23 , hence x >= 2^23 and this equal to 8388608 , Now to the eyes widening fun fact for any value for x where x >= 8388608 , we have x +1 = x , here x is single and might be better to be written 8388608.0 , yet i think the idea is received, if you do 8388608.0 + 1.0 then the result will be 8388608.0 !! with that fun fact at hand imagine using single or double, any float numbers to count puppies in a country, we will thing we increasing by one and will hit hard limit without any notice or problem. same if we are counting pairs of eyes of puppies the there is limit when the step by 2 will be neglected and lost. as for bigger numbers as shown above we might be adding millions or billions and losing money, that why banks and financial software should never use floating point and stick to fixed point. Now back to our non beloved puppies killer SameValue, and here my thoughts 1) It does use Epsilon in violation to what epsilon represent, there is Machine Epsilon mentioned above, but Epsilon generally in math used to describe small values no Deltas, yes in that context and as it implemented it should be named Delta, that name trigger my OCD. 2) SameValue calculate the Delta between singles not THE approximation for equality, not the sameness with tolerance, just Delta, so if you feed a number like 1000 then you might expect the close one at 1000.01 with %1 error tolerance, this Delta or Epsilon make no sense when the number is 1000 000 because it will be Delta and will compare between 1000 000 and 1000 000.01 , can you spot the different here ? we are using the same delta and changed the tolerance to %0.0001 , this goes up and down, and the only way to accommodate this factor is by adjusting the Delta (Epsilon for the SameValue) at runtime, while this can be ignored or pre-calculated for assertion, it is a must for runtime if you value your calculations and output precision. 3) the documentation fails to mention the delta usage and fail to warn about this very shortage. 4) The default value for error tolerance if we might call it for SameValue, is 1e-7*1000= 1e-4 = 0.0001 , for single type, this is useless when numbers goes in thousands or millions, but the real danger is for small numbers like the ones which are less than 0.0001 , they all are equal with SameValue ! 5) Rollo62 mentioned known the arena we staging and performing within, yet for a small office or room where 4x5 m we can use the same tolerance (we put it there like 0.01 for 1 cm) for the dimensions and the area and we still in control, but if we used the tolerance E=0.01 (1e-2) , with huge farm land with where 1000x1000m we have million squared meter and we are in different realm like the example in (2), notice for larger numbers above 8 millions and from above too, 1 meter tolerance is literally no sense operation. Now from these i can deduce this, SameValue is wrong, Anders asked if it is within its implementation, and yes it is but it has deceiving name, it should be called something CloseEnoughBy , and never should be used for testing equality or approximation of equality, it failed in documentation and failed in implementation to serve the sameness which it claim, as for its usage and how to use it right, then you have to calculate the correct and right Epsilon then provide it as parameter, rendering its usage merely compare the difference, aka within Delta. In my opinion SameValue should 1) deprecated, just kill it and warn who use it to refer to the documentation. 2) add a real, useful and correct alternative like SameValueEx or some other name. 3) Yes like Anders point to, the implementation should test first for 0 and NaN , in other words it should be right implemented, and this mean with the suggested formula it will be slower a lot, like a lot, but and as always correctness can't and must not be a point to discuss, the developer can and should seek alternative if it will be used in big/long tight loop, heck he can use SameValue 🙂 killing more puppies 😭 4) The enhanced version could have multiplier (cofactor) for Epsilon instead of Epsilon, as it should be strictly using Machine Epsilon, the multiplier can serve better in implementation, example if you are calculating area then two singles involved hence we can adjust the positive only multiplier to adjust to the area, thus we have clearer and easier reach for the best error tolerance, and here i don't want to drive this of the road as multiplier from mathematically and statistically the correct way should be accurately adjusted, see, in case of 2d and area calculation there is many methods for error tolerance from CEP to Root Mean Square Error to .... but we talk singles and many of those doesn't apply on the single arithmetic operation itself those apply to the input values for the arithmetic operation and for the operation it self we are little more free, this is due the fact float point are approximations so we can just use a factor of 2 or you can go with sqrt(2) or even 10, this is up to the developer and the engineer of the formula/operation involved. Hope that was clear and somebody find it useful ! ps: more things came to mind now, where i saw many made such mistake, see, single can hold a large number like 340282346638528859811704183484516925440.0 and small number like subnormal 2^−149 = 0.00000000000000000000000000000000000000000000140129846432481707092372958328991613128026194187651577..... But it can't have 340282346638528859811704183484516925440.6 in fact the closest number to 340282346638528859811704183484516925440.0 is the one i listed above and it is far by 33408896097170127936.0 So be careful when and how you use float point and remember one thing float point is like ruler (exponential one) that slide up and down losing anything not in the range, well mostly it slide up only killing all creature including puppies standing behind the point. PS i made a mistake above and will not correct it, but will point to it here and too lazy to read and rewrite, i dont think this change a lot of the above though. I used ">=" instead of ">" in calculating ULP and this leads to mistakenly assuming adding one to 8388608.0 will result the same, while with ">" the needed exp will be 24 so 16777216, and here is the testing code procedure TestULPx; var A, B, C: Single; begin A := 8388608.0; B := 1.0; C := A + B; Writeln(C); A := 16777214.0; B := 1.0; C := A + B; Writeln(C); A := 16777215.0; B := 1.0; C := A + B; Writeln(C); A := 16777216.0; B := 1.0; C := A + B; Writeln(C); end; The output 8.38860900000000E+0006 1.67772150000000E+0007 1.67772160000000E+0007 1.67772160000000E+0007 // 16777216.0 is the threshold at 2^24 when adding one is lost, x+1=x Share this post Link to post
Rollo62 581 Posted 2 hours ago 15 hours ago, Anders Melander said: I would argue that the type of Epsilon depends on the use case; For one an absolute explicit Epsilon is suitable (SameValue(Value, Epsilon)), for another an absolute implicit Epsilon will make sense (SameValue(Value) but with some better defaults), and for yet another a relative magnitude Epsilon would be desirable (let's call it KasObSameValue(Value, Epsilon) since we don't have it in the RTL). What I wanted to say is, that even for SameValue(Value), you have to consider the Epsilon. Only because its some kind of default, it doesn't make it better, you should better know it and deal with it. https://docwiki.embarcadero.com/Libraries/Athens/en/System.Math.SameValue Quote If Epsilon = 0, then some reasonable default value is used implicitly. For example, the Double version of SameValue uses the default value: Epsilon = Max(Min(Abs(A), Abs(B)) * 1E-12, 1E-12) What exactly is reasonable and whats not, depends on the use case, like you perfectly said. Thats why I would argue, that you always shall consider it (and better set it to make it visible to the outside too). I prefer to pre-set Epsilon always by myself, to the desired value, instead of any automatic and maybe unexpectedly changing behaviour from internal structures. This is only calling for unwanted sideeffects. 2 hours ago, Kas Ob. said: i will start with single float point, it is 32bit and we all know its structure and how it work, but i am writing about the things that are not very known ! lets look at the smallest number that single can have, and again i am talking about positive only here as the negative while they are less but ,, meh ,, it is just signal bit, anyway the 3 smallest numbers, and here pay attention i am going with normal and not subnormal ones, these are smaller a lot but they are different case, while both belongs to IEEE 754, the smallest one is 2^−126, i don't have calculator to get the decimal value because it will help here, so i am using online Wolfram Alpha 2^-126 = 1.1754943508222875079687365372222456778186655567720875215087517062784172594547271728515625 × 10^-38 but we are limited in precision and i need the decimal representation and singles has 24 precision so 2^-126 = 0.000000000000000000000011754943508222875079687365372222456778186655567720875215087517062784172594547271728515625 All this might be true, but is very scientific. Unless I cannot see a real benefit, I will stay with my decimal Epsilon, perhaps only in deeper Physics we would need different perspectives than 10^-3, ... 10^-15 or so. Share this post Link to post