Approximate equality for Rosetta

I noticed that Processing is missing from https://rosettacode.org/wiki/Approximate_equality.
Sure this can be improved, but here is a start…
I have used ‘big’ not in a vain attempt to avoid floating point errors, but to give expected results.

int big = 1000000000;  


void setup() {
  noLoop();
}

void draw() {
  isClose(1.2, 1.1, 0.10000001); // first number, second number, how close is close
}

void isClose(float num1, float num2, float prox) {

  int intnum1 = int(num1*big);
  int intnum2 = int(num2*big);
  int intprox = int(prox*big);

  int intdiff = abs(intnum2 - intnum1);
  //println(intdiff);
  //println(intprox);

  if (intdiff < intprox) {
    print("is close");
  } else {
    print("is not close");
  }
}

This is very limited. ‘Big’ cannot be bigger because it goes out of range for type int so this will only work for numbers with fewer than four decimal places.

I want Processing to be represented at https://rosettacode.org/wiki/Approximate_equality but not poorly, so am appealing to @kll @GoToLoop @Chrisir @jeremydouglass @Misha.studio

long is another type for variables

It’s like int but longer

1 Like

Interesting problem. Looking at Rosetta code it seems to me that closeness is based on the number of significant digits (sd) to use in the comparison. Doing this means that the closeness is related to the magnitude of the numbers involved.

This sketch provides a simple function to perform the comparison based on the number of significant digits. I should point out that the number of significant digits should be in the range 1 to ~8 for the Java float data type.

void setup() {
  testIsClose(1.201, 1.202, 4);
  testIsClose(1201, 1202, 3);
  testIsClose(0.0034567, 0.0034567, 6);
  testIsClose(0.00345678, 0.00345679, 6);
  testIsClose(-sqrt(2)*sqrt(2), - 2, 8);
  exit();  // we are done so close sketch window
}

void testIsClose(float n1, float n2, int sd) {
  boolean result = isClose(n1, n2, sd);
  if (result)
    println(n1, " is close to ", n2, " using ", sd, " significant digits");
  else
    println(n1, " and ", n2, " are not close using ", sd, " significant digits");
}

boolean isClose(float num1, float num2, int sd) {
  float epsilon = max(num1, num2) / pow(10, sd);
  return abs(num1 - num2) <= epsilon;
}
1 Like

@quark Your code is excellent, would you like to post it at Rosetta?

But, I would like (please) to argue for a different approach. I can think of at least two examples where isClose would be very useful. When mixing colours you only want to mix as accurately as the eye can see. Humans do not discern very small differences in RGB values. A number can be given to this acuity.

In 3D modelling, geodata, prototyping, etc. one often tests for coplanarity among a number of points in space. You want to know if a number of points are (more or less) in the same plane and you do not want an error reported because of floating point error. I do not want a result of “Is not coplanar” because of a floating point error or because one point is some very small distance away from a mathematically precise equality. So in these two examples and more, the user wants to test for ‘close enough’ against a distance. A test based on the number of significant digits seems more like a test of the accuracy or inaccuracy of the float number.

So, how would one do this?
Could this approach (below) work? The idea is to make the numbers before and after the decimal point into an integer and then use the integers for comparison with an integer distance.

void draw() {
  float num1 = sqrt(2);

  String str1 = str(num1); // number to string
  String[] splt1 = split(str1, '.');   //split into before and after
  int bef1 = int(splt1[0]); // integer of numbers before the decimal point
  int aft1 = int(splt1[1]); // integer of numbers after the decimal point


  // in case I need some leading zeros
  int l1 = splt1[1].length(); //length of number after decimal point
  int lcheck1 = str(aft1).length(); //length of number I have
  int numberzeros = l1 - lcheck1;


  println("integer number before decimal point:", bef1); // 1
  println("integer number after decimal point:", aft1); // 4142135
  println("leading zeros:", numberzeros);  // 0
}

I disagree.

Using significant digits allows for great flexibility in our comparison. For instance if sd = 4 then the two variables are considered equal if the difference between them is 1/10000 of the numbers being compared, if sd = 6 then it would be 1/1000000. This will work irrespective the magnitude of the numbers being compared.

The sketch below is an improvement on my original code in that the programmer can select between significant digits or a simple distance (measure of closeness) when performing the test. It seems to me that one or other of these functions can be used in any algorithm where there is a need to test for equality between floating point numbers.

With regard to your suggested approach requires the number to be converted to a string during the process and this introduces other problems

  • Converting a number to a string is programming language dependent
  • The string representation for floating point numbers can depend where in the world the program is running. For example, in France the comma (,) is used as the decimal separator
  • Processing and Java provide a number of methods to convert a number to a string with different results.
  • Very large and very small numbers use scientific notation so 1.23E35 is a valid string representation of a large floating point number. Your approach would have to handle these also.

Still it is always fun to think of new or alternative ways to do things :grinning:

void setup() {
  // These test pass a integer as the third parameter so is treated as 'disgnifcant diigits'
  println("User specifies number of significant digits");
  testIsClose(1.201, 1.202, 4);
  testIsClose(1201, 1202, 3);
  testIsClose(0.0034567, 0.0034567, 6);
  testIsClose(12345678, 12345679, 7);
  testIsClose(-sqrt(2)*sqrt(2), - 2, 8);
  // These pass a float as the third parameter so treated as a measure of closeness
  println("\nUser specified closeness factor");
  testIsClose(12345676, 12345678, 1f);
  testIsClose(-sqrt(2)*sqrt(2), - 2, 1e-6);
  exit();  // we are done so close sketch window
}


/*
These are the two methods used to test for exactness they use method overloading. 

If the third parameter is an integer then it will be treated as the number of 
significant digits and the first method is executed.

If the third parameters a float then it will be treated as the measure of 
closeness or maximum distance allowed between the variables
*/

boolean isClose(float num1, float num2, int sd) {
  return abs(num1 - num2) <= max(abs(num1), abs(num2)) / pow(10, sd);
}

boolean isClose(float num1, float num2, float epsilon) {
  return abs(num1 - num2) <= epsilon;
}


// Utility functions to test the isClose() function

void testIsClose(float n1, float n2, int sd) {
  boolean result = isClose(n1, n2, sd);
  if (result)
    println(n1, " is close to ", n2, " using ", sd, " significant digits");
  else
    System.err.println(n1 + " and "+ n2 + " are not close using "+ sd + " significant digits");
}

void testIsClose(float n1, float n2, float epsilon) {
  boolean result = isClose(n1, n2, epsilon);
  if (result)
    println(n1, " is close to ", n2, " using ", epsilon, " as closeness factor");
  else
    System.err.println(n1 + " and " + n2 + " are not close using " + epsilon + " as closeness factor");
}
1 Like

A master piece! I don’t think I should post it to Rosetta, when the code is yours. Would you like to post it?

I guess I have been conflating two problems. One problem is the choice between significant digits and a measure of closeness. You have solved that!!! The other, and unrelated problem is the difference between the representation of a float number and what number is being used in the function. I didn’t want to believe we are comparing ‘cherry’ and ‘orange’ when we are comparing ‘cereza’ and ‘naranja’. For example, a float 4.000345678 prints as 4.0003457. But I guess the differences are small and everyone (but me) knows about and accepts these errors.

If I wanted to know whether a terrain was planar whilst also taking into account the texture of the surface one would hope the roughness of the surface is greater than the roughness of the floating point numbers.

I agree with your points about converting a float number to a string and then to an integer.

I did it with Doubles to integer (see below) and with a little more work, to ensure numbers are the correct length, etc., I could start with Doubles, then do the comparisons with integers. But your solution is the way to go.

String zeros = "0000000000";


void setup() {
  noLoop();
  double2integer(4.00345678D);
}


void draw() {
}

// turn a double into an int



void double2integer(double foo) {

  String mb = "" + foo;
  String[] mc = split(mb, '.');   //split into before and after
  int md = int(mc[0]); // integer of numbers before the decimal point
  int me = int(mc[1]); // integer of numbers after the decimal point

  // in case I need some zeros
  int mf = mc[1].length(); //length of number after decimal point
  int mg = str(me).length(); //length of number I have
  int mh = mf - mg; // number of missing zeros

  String p1 = str(md);
  String p2 = zeros.substring(0, mh); // could test that mh < 10
  String p3 = str(me);

  int y = int(p1+p2+p3);
  println("integer:", y); // integer: 400345678
  // moved 8 places

}

Please will you help me understand the flow through your code (cut down below). We start at line 2. The third parameter is a float. We have called one of the two testIsClose functions, so we could go to line 16 or we could go to line 24. So far we have not encountered a boolean, so how is this decided?

void setup() {
  testIsClose(12345676, 12345678, 2.0); // float
  exit();  // we are done so close sketch window
}

boolean isClose(float num1, float num2, int sd) { //integer
  return abs(num1 - num2) <= max(abs(num1), abs(num2)) / pow(10, sd);
}

boolean isClose(float num1, float num2, float epsilon) {
  return abs(num1 - num2) <= epsilon;
}

// Utility functions to test the isClose() function

void testIsClose(float n1, float n2, int sd) { // integer
  boolean result = isClose(n1, n2, sd);
  if (result)
    println(n1, " is close to ", n2, " using ", sd, " significant digits");
  else
    System.err.println(n1 + " and "+ n2 + " are not close using "+ sd + " significant digits");
}

void testIsClose(float n1, float n2, float epsilon) {
  boolean result = isClose(n1, n2, epsilon);
  if (result)
    println(n1, " is close to ", n2, " using ", epsilon, " as closeness factor");
  else
    System.err.println(n1 + " and " + n2 + " are not close using " + epsilon + " as closeness factor");
}

If the third parameter is a float then the program flow is line
2
24
25
10
11 returns either true or false
26
27 or 29 depending on value of result
3 sketch terminates

If the third parameter is an integer then the program flow is
2
16
17
6
7 returns either true or false
18
19 or 21 depending on value of result
3 sketch terminates

If there are 2 or more functions with the same name then Java examines the data types of the parameters to decide which to call. This is called method overloading

2 Likes

That is super cool, thank you. Before adding this to Rosetta, perhaps we should discuss what is Processing and what is Java being used in Processing, or does it not matter? I am thinking that maybe the System.err.println should be println and I do know whether method overloading is pure Processing or not. If not, is there a Processing method? I will experiment.

1 Like

I don’t plan to add it to Rosetta as there is already an answer for Java.

In Processing it is possible to write sketches in several programming languages depending on the current mode selected. When Processing was first created there was only one mode ‘Java’, so you had to program using Java, sort of!.

Java is an object orientated (OO) language that has a steep learning curve and Processing wasn’t aimed at pure computer programmers, instead target users were graphic artists (and beginners) interested in creating computational art.

Although Processing code is actually Java it is missing some very important boilerplate code that enables the sketch to run on your computer. When you launch your sketch Processing has a preprocessing step that adds the necessary Java code before compilation and execution.

Processing is in fact a Java library providing many methods to reduce the verbosity of Java for instance

Processing                  Java equivalent
println                     System.out.println
sqrt(...)                   Math.sqrt(...)
int(n)                      (int) n

System.out.println sends data to the standard output stream whereas System.err.println sends data to the stadard error stream. I used this because it displays the output in a different colour and Processing doesn’t have a short cut for it.

Now Java has two primitive data types for floating point numbers float and double that require 4 and 8 bytes respectively. All Java math libraries use double by default because it has 15-16 digit accuracy as opposed to float that has only 7-8 digit accuracy.

Processing uses the float data type by default whereas Java uses the double data type so

sqrt(2)   // returns the square root of 2 as a float (Processing function) but
Math.sqrt(2)   // returns the square root of 2 as a double (Java math library function.

Java supports method overloading, so Processing (Java mode) supports method overloading. In fact anything you do in Java can be done in Processing (Java mode).

Not all languages support method overloading, for example Javascript doesn’t although there are ways to do something similar.

If you are uncomfortable using method overloading you can simply use different names for the two isClose methods.

1 Like

That all makes perfect sense, thank you so much! I am not uncomfortable using method overloading, just curious to understand what counts as Processing and what does not. I now see.

You say there is already an answer for Java but thinking of non-programmers (like me) who come to Rosetta, scroll down for the Processing solution and see nothing. They/we would not think to look at the Java solution. So, I plan to add my crude solution (below) using Doubles. Please feel free to comment on my code before I post, or to delete my entry and replace with your far better solution.

double epsilon = 1e-18D;

void setup() {
  isClose(100000000000000.01D, 100000000000000.011D, epsilon);
  isClose(100.01D, 100.011D, epsilon);
  isClose(10000000000000.001D / 10000.0D, 1000000000.0000001000D, epsilon);
  isClose(0.001D, 0.0010000001D, epsilon);
  isClose(0.000000000000000000000101D, 0.0D, epsilon);
  isClose(sqrt(2) * sqrt(2), 2.0D, epsilon);
  isClose(-sqrt(2) * sqrt(2), -2.0D, epsilon);
  isClose(3.14159265358979323846D, 3.14159265358979324D, epsilon);
  
  exit();  // all done
}




void isClose(double num1, double num2, double epsilon) {
  double diff = num2 - num1;

  if (diff < 0) { // abs expects floats
    diff = -1 * diff;
  }

  if (diff < epsilon) {
    println("True. ",num1,"is close to", num2);
  } else {
    println("False. ", num1,"is not close to", num2);
  }

}

Three things you might consider

  1. In Processing sqrt(2) returns a float so use Math.sqrt(2) instead e.g. isClose(Math.sqrt(2) * Math.sqrt(2), 2.0D, epsilon);
  2. Instead of abs(float) use Math.abs(double) because it returns a double
  3. In my example the isClose method returns a boolean and has no unnecessary code. This means it can be copied directly into someones sketch and can also be used as a condition inside the if statement e.g. if(isClose(...))){ ...

If you are going to create a Processing version for Rosetta code you might just use the Processing methods and float variable because many users don’t use the double data type or are not aware of it.

1 Like

Wow, I did not know you could use a Math library like that. I am used to Python so assumed I would have to import Math before I could use it but, as you have already explained, Java is already available.

I think I have followed your instructions (thank you!) [see below]. But in your version the if else statement has no curly brackets. How? Why?

As I am using Double and Math, perhaps I should also use System.err.println.

Of course you are right, I should use float but I just don’t have the heart.

double epsilon = 1e-18D;

void setup() {
  testIsClose(100000000000000.01D, 100000000000000.011D, epsilon);
  testIsClose(100.01D, 100.011D, epsilon);
  testIsClose(10000000000000.001D / 10000.0D, 1000000000.0000001000D, epsilon);
  testIsClose(0.001D, 0.0010000001D, epsilon);
  testIsClose(0.000000000000000000000101D, 0.0D, epsilon);
  testIsClose(Math.sqrt(2) * Math.sqrt(2), 2.0D, epsilon);
  testIsClose(-Math.sqrt(2) * Math.sqrt(2), -2.0D, epsilon);
  testIsClose(3.14159265358979323846D, 3.14159265358979324D, epsilon);

  exit();  // all done
}


boolean isClose(double num1, double num2, double epsilon) {
  return Math.abs(num2 - num1) <= epsilon;
}


void testIsClose(double num1, double num2, double epsilon) {
  boolean result = isClose(num1, num2, epsilon);

  if (result) {
    println("True. ", num1, "is close to", num2);
  } else {
    println("False. ", num1, "is not close to", num2);
  }
}

The {} are used to delineate a block of code which can include 1 or more lines. If the block comprises of just one line then the {} are not needed

If statement with a code block of 2 statements

if(condition){
    // statement executed if condition is true
    // statement executed if condition is true
}
// statement to be executed when if statement has finished

If statement with a code block of 1 statement, note that {} are not required

if(condition)
    // statement executed if condition is true
// statement to be executed when if statement has finished
1 Like

Anything directly under package “java.lang”, like class Math for example, is auto-imported by Java.

But as @quark has already pointed out, neither double nor long primitive types have much support within Processing’s API.

So for a more authentic Processing Rosetta code, stick w/ float & int primitive types instead.

Leave double & long for actual Java Rosetta examples.

3 Likes