0

I was looping through an if statement for an object that decayed over time, taking the value each time and subtracting -0.2. After the first time through the number would get all bizarre like 0.9653677422, instead of staying as multiples of 0.2 (4.2, 4.0, 3.8, 3.6...etc).

The initial value was 7.

Example:

if (this.health > 0){
    this.health = this.health - 0.2;
}else{
    return;
}

Why would subtracting -0.2 from a value with processing.js turn an integer into an irrational number?

Edit*** Via adonike's answer I learned that decimals can't be represented with 100% accuracy using floating binary point numbers. By keeping all of the numbers as integers (multiply the initial value by 10 and also the value being subtracted) the same decay rate could be retained (1/35 per loop) without losing precision. The numbers became 70 for the initial value and 2 for the decay rate (number subtracted) & everything worked out.

It could also just be 35 for initial value and 1 for the number subtracted with the same desired result.

For additional insight into this see: Why can't decimal numbers be represented exactly in binary? and also the links provided in Kevin Workman's answer.

The code that this question derived from is shown below:

var Tile = function(x, y, sourceImg) {
    this.x = x;
    this.y = y;
    this.width = 25;
    this.health = 35;
    this.healthMax = this.health;
    this.decayRate = 1;
    this.daysOld = 0;
};

and

Tile.prototype.drawPlantTiles = function() {
        if (this.health > this.healthMax/2){
            this.occupied = true;
            fill(168, 118, 25);
            stroke(163, 109, 21);
            rect(this.x, this.y, this.width, this.width,0);
            strokeWeight(1);
            noStroke();
            image(plantImg1, this.x, this.y, this.width, this.width);
            this.health = this.health - this.decayRate;
            this.daysOld++;
        } else if (this.health > this.healthMax/4 && this.health <= this.healthMax/2){
            this.occupied = true;
            fill(168, 118, 25);
            stroke(163, 109, 21);
            rect(this.x, this.y, this.width, this.width,0);
            strokeWeight(1);
            noStroke();
            image(plantImg2, this.x, this.y, this.width, this.width);
            this.health = this.health - this.decayRate;
            this.daysOld++;
        } else if (this.health > 0 && this.health <= this.healthMax/4){
            this.occupied = true;
            fill(168, 118, 25);
            stroke(163, 109, 21);
            rect(this.x, this.y, this.width, this.width,0);
            strokeWeight(1);
            noStroke();
            image(plantImg3, this.x, this.y, this.width, this.width);
            this.health = this.health - this.decayRate;
            this.daysOld++;
        }else{
            fill(168, 118, 25);
            stroke(163, 109, 21);
            rect(this.x, this.y, this.width, this.width,0);
            strokeWeight(1);
            noStroke();
            this.state = false;
            return;
        }
};

Note: the "this.healthMax" thresholds still need to be reworked to remove all decimals / maintain 100% precision.

Community
  • 1
  • 1
Xx2ixX
  • 21
  • 1
  • 5

2 Answers2

1

The other answer is correct, but I'd like to provide some more detail.

Here is the go-to article for this question: What Every Computer Scientist Should Know About Floating-Point Arithmetic

Here is a shorter, JavaScript-specific version: What Every JavaScript Developer Should Know About Floating Points

The article lists a few libraries that are workarounds for the problem:

See also:

Community
  • 1
  • 1
Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
0

That's because floating point arithmetic isn't 100% accurate. There's no way to represent decimals in a binary form.

In javascript all numbers are treated as 64bit floating point.

To help in your particular case (using increments of 0.2 or -0.2) you can multiply by 10 before you do the increment, operate and then divide by 10:

if (this.health > 0){
this.health = (this.health * 10 - 2) / 10;
}else{
return;
}
adonike
  • 1,038
  • 10
  • 8
  • Thanks that makes sense. I actually had done exactly what you recommended (multiplying everything by 10 to avoid decimals) and it worked out. Good to know – Xx2ixX Sep 15 '16 at 12:33