Adding 0.1 with double and float is not accurate

Hi. I wanted a "for" loop from 0 to 1 incrementing by 0.1. Sample code is shown below but the maths is so inaccurate it doesn't reach 1.0. This appears to be a fundamental defect. I can repeat this in Processing-3.02, Processing-2.2.1 and with different Java versions. I get the same result on my Windows laptop and on my iMac.

There is also some inaccuracy when I repeat this test in pure Java (1.7.0_79) using Eclipse (Mars) but it is not quite as inaccurate.

In either case I would expect to add 0.1 with better accuracy.

Processing code:

import processing.opengl.*;

void setup() {

  size( 480, 360);
  print("double ");
  for (double d=0; d<=1.0 ; d+=0.1) {
    print(" ",d);
  }
  println();
  print("float ");
  for (float f=0; f<=1.0 ; f+=0.1) {
    print(" ",f);
  }
  println();
  print("i/10.0 ");
  for (int i=0; i<=10 ; i++) {
    double d2 = i/10.0;
    print(" ", d2);
  }
  println();
  print("i/(float)10.0 ");
  for (int i=0; i<=10 ; i++) {
    float f2 = i/(float)10.0;
    print(" ", f2);
  }

}

Processing Results:

double   0.0  0.10000000149011612  0.20000000298023224  0.30000000447034836  0.4000000059604645  0.5000000074505806  0.6000000089406967  0.7000000104308128  0.800000011920929  0.9000000134110451
float   0.0  0.1  0.2  0.3  0.4  0.5  0.6  0.70000005  0.8000001  0.9000001
i/10.0   0.0  0.10000000149011612  0.20000000298023224  0.30000001192092896  0.4000000059604645  0.5  0.6000000238418579  0.699999988079071  0.800000011920929  0.8999999761581421  1.0
i/(float)10.0   0.0  0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1.0

Java Code:

public class LoopTest {

    public static void main(String[] args) {
          System.out.print("double ");
          for (double d=0; d<=1.0 ; d+=0.1) {
              System.out.print(" "+d);
              }
          System.out.println();
          System.out.print("float ");
              for (float f=0; f<=1.0 ; f+=0.1) {
                  System.out.print(" "+f);
              }
              System.out.println();

              System.out.print("i/10.0 ");
              for (int i=0; i<=10 ; i++) {
                double d2 = i/10.0;
                System.out.print(" "+d2);
              }
              System.out.println();

              System.out.print("i/(float)10.0 ");
              for (int i=0; i<=10 ; i++) {
                float f2 = i/ (float)10.0;
                System.out.print(" "+f2);
              }
              System.out.println();

    }

}

Java Results;

double  0.0 0.1 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 0.8999999999999999 0.9999999999999999
float  0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.70000005 0.8000001 0.9000001
i/10.0  0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
i/(float)10.0  0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

Any thoughts as to why such a simple calculation is so inaccurate?

:-S

Answers

  • edited May 2016 Answer ✓

    println("double: ");
    for (double d = 0d; d <= 1d; d += .1d)  print(d, TAB);
    
    println("\n\nfloat: ");
    for (float f = 0.0; f <= 1.0; f += .1)  print(f, TAB);
    
    println("\n\ni/10d: ");
    for (int i = 0; i <= 10; ++i) {
      double d = i/10d;
      print(d, TAB);
    }
    
    println("\n\ni/10f: ");
    for (int i = 0; i <= 10; ++i) {
      float f = i/10.0;
      print(f, TAB);
    }
    
    exit();
    

    double:  
    0.0     0.1     0.2     0.30000000000000004     0.4     0.5     0.6     0.7     
    0.7999999999999999  0.8999999999999999  0.9999999999999999
    
    float:  
    0.0     0.1     0.2     0.3     0.4     0.5     0.6     0.70000005  
    0.8000001   0.9000001
    
    i/10d:  
    0.0     0.1     0.2     0.3     0.4     0.5     0.6     0.7     0.8     0.9     1.0
    
    i/10f:   
    0.0     0.1     0.2     0.3     0.4     0.5     0.6     0.7     0.8     0.9     1.0
    

  • Any thoughts as to why such a simple calculation is so inaccurate?

    The accuracy is better than 0.0001% which is very, very good.

    Java like many languages uses 4 bytes to store a float that 32 bits which gives just 4,294,967,296 different combinations to store an infinite number of floating point so some innacuracy is to be expected.

  • edited May 2016 Answer ✓

    The float n.000000000r occurs because of rounding errors in the the mantissa IIRC. A floating point number is written by the machine as a scientific number like 123*10^7. Rounding errors make those weird artifacts with floats.

    To not have errors, use BigDecimal.

        /*  EXAMPLE: Using BigDecimals.
        // BigDecimal is used for accurate arithmetic of any real number.
        // BigDecimal is intended to work with databases and very long numbers.
        // It can calculate a floating point number with desired precision, without errors.
        // If your project requires exact numbers and is not real-time oriented, then BigDecimal is appropriate.
        */
    
        import java.math.BigDecimal; 
    
        void setup() { 
          float a = 2; 
          float b = 32;
    
          // Demonstration of wrapper functions. 
          float multiply = m(a, b);  // x = 2 * 32.
          float division = d(a, b);  // x = 2 / 32.
          float addition = a(a, b);  // x = 2 + 32.
          float subtract = s(a, b);  // x = 2 - 32.
    
          // Some weird number showing improper function use.
          float improper = m(s(d(a,b),a(m(b,a),a)),s(b,a)); 
    
          // Equally confusing, but still the same number as above.
          float why = ((a/b)-((a*b)+a))*(b-a);
    
          // Accuratly calculate the inverse of this massive number to the 54th decimal place.
          String x = "189327469745023743269873248976983274538.745895634294783274486662066354334679694";
                 x = div("1.0", x, 54); 
    
          // Floating Point Rounding Error Demonstration. 
          double i = 189327469745023743269873248976983274538.745895634294783274486662066354334679694;
                 i = 1.0 / i; // Should be same operation as above, but instead finds a slightly different number faster.
    
          println(multiply);       
          println(division);       
          println(addition);       
          println(subtract);      
          println(improper);  
          println(why); 
          println(x);        // 5.281853|718040743E-39     Note that x != i.
          println(i);        // 5.281853|688555732E-39     Pipe inserted to highlight accuracy.
          exit(); 
        }
    
        // Multiply Wrapper Function so we don't get carpal tunnel syndrome writing these for every operation.
        float m(float a, float b) {
          BigDecimal BigDec = new BigDecimal(str(a));  // Always convert value to string when calling BigDecimal.
                     BigDec = BigDec.multiply(new BigDecimal(str(b))); 
          return BigDec.floatValue();
        }
    
        // Standard Divide. Floats round their numbers after 6 places so 8 decimal places is enough for most conditions.
        float d(float a, float b) {
          BigDecimal BigDec = new BigDecimal(str(a)); // numerator.divide(denominator, nthDecimalPlace, roundingStyle);
                     BigDec = BigDec.divide(new BigDecimal(str(b)), 8, BigDecimal.ROUND_HALF_UP);
          return BigDec.floatValue();
        }
    
        // String Divide
        String div(String a, String b, int decimalPlace) {
          BigDecimal BigDec = new BigDecimal(a); // numerator.divide(denominator, nthDecimalPlace, roundingStyle);
                     BigDec = BigDec.divide(new BigDecimal(b), decimalPlace, BigDecimal.ROUND_HALF_UP);
          return BigDec.toString();
        }
    
        // Addition
        float a(float a, float b) {
          BigDecimal BigDec = new BigDecimal(str(a));
                     BigDec = BigDec.add(new BigDecimal(str(b)));
          return BigDec.floatValue();
        }
    
        // Subtraction
        float s(float a, float b) {
          BigDecimal BigDec = new BigDecimal(str(a));
                     BigDec = BigDec.subtract(new BigDecimal(str(b)));
          return BigDec.floatValue();
        }
    
  • edited May 2016 Answer ✓

    Using BD for loops:

          for(float f=0.0; f<1.0; f=a(f, 0.1)) println(f);
    
  • Thanks to you all for your answers.

    I see that there is a general problem representing decimals, and I can see there is lots of information out there on this topic.

    If I use d += 0.1d the Processing result is then the same as Java.

    I will use BigDecimal in future where I need accuracy.

Sign In or Register to comment.