0

Im checking a certain amount of random pixels in imageData to get the ratio of black and white. When I use the

Math.round(num * Math.pow(10, decimalPlace)) / Math.pow(10, decimalPlace);

formula to get the number to 3 figures, some times the output looks something like this, "7.500000003%". Can anyone please explain this? BTW, the num.toFixed(decimalPlaces) is not what i am looking for, since sometimes I will have an output like "77.5123123" and that function would make "77.512". I would like to 3 significant figures.

The tabbing is a bit weird in the snippet, dont know why. Code snippet:

var can, ctx, w, h, boxes = []
;
window.onload = function() {
  can = document.getElementById("can"); ctx = can.getContext("2d");
 can.width = w = window.innerWidth; can.height = h =     window.innerHeight;
  var amount = 3;
  
  for(var i = 1; i < amount+1; i++) {
  var x = w/(amount+1)*i, y = h/5, width = height = Math.round(Math.random()*w/Math.pow(amount, 2)+25);

  boxes.push(new Box(x, y, width, height));
  }
  
  console.log("White : Black : Anomaly");
 console.log(" ");
 console.log("-------Circles-------");
 console.log(" ");

 var dec = Math.pow(10, 3);

 boxes.forEach(function(ob) {
  ob.draw();
  var result = getRatios(getPixels(ob));
  console.log(Math.round(result[0]*dec)/dec*100+"% : "+Math.round(result[1]*dec)/dec*100+"% : "+Math.round(result[2]*dec)/dec*100+"%");
 });
}

function getPixels(ob) {
 var imageData = ctx.getImageData(ob.x, ob.y, ob.width, ob.height)
 return imageData.data;
}

function getRatios(data) {
 var hitWhite = 0, hitBlack = 0, hitAnomaly = 0;
 var reps = Math.round(data.length/4/10);

 for(var i = 0; i < reps; i++) {
  var pixel = Math.round(Math.random()*data.length/4);
  var r = data[pixel*4], g = data[pixel*4+1], b = data[pixel*4+2];

  if(r == 255 & g == 255 & b == 255) { hitWhite++; }
  else if(r == 0 & g == 0 & b == 0) { hitBlack++; }
  else { hitAnomaly++; }
 }

 return [hitWhite/reps, hitBlack/reps, hitAnomaly/reps];
}

class Box {
 constructor(x, y, width, height) {
  this.x = x; this.y = y; this.width = width; this.height = height;
  this.cx = this.x+this.width/2; this.cy = this.y+this.height/2; this.r = this.width/2; this.angle = Math.PI*2;
 }

 draw() {
  ctx.fillStyle = "white";
  ctx.fillRect(this.x, this.y, this.width, this.height)

  ctx.fillStyle = "black";
  ctx.beginPath()
   ctx.arc(this.cx, this.cy, this.r, 0, this.angle, true)
   ctx.fill();
  ctx.closePath()
 }
}
canvas { background: rgb(50, 50, 50); }
<canvas id="can"></canvas>
Lukas Knudsen
  • 197
  • 2
  • 3
  • 14
  • Possible duplicate of [round to 3 decimal points in javascript/jquery](https://stackoverflow.com/questions/33429136/round-to-3-decimal-points-in-javascript-jquery) – Heretic Monkey Jun 07 '18 at 19:49
  • Since I am asking why this occurs, then it has nothing to do with the post you linked. – Lukas Knudsen Jun 07 '18 at 19:51
  • The reason why this occurs: https://stackoverflow.com/questions/588004/is-floating-point-math-broken – Barmar Jun 07 '18 at 20:21
  • Floating point is approximate, and dividing by powers of 10 often results in artifacts like this because they can't be represented exactly as binary fractions. – Barmar Jun 07 '18 at 20:22

1 Answers1

2

As pointed in comments the issue occurs because Number in JavaScript is standard floating-point base 2 value and exact base 10 fractions almost never can be represented as exact base 2 fractions (with a few exceptions like 0.5 or 0.25). It means that even after rounding your value is not exactly what you want and can't be it. One solution would be using some library that implements "decimal" floating point or fixed point numbers. But for your purposes you probably can use toPrecision method which returns

A string representing a Number object in fixed-point or exponential notation rounded to precision significant digits

Updated demo:

var can, ctx, w, h, boxes = []
;
window.onload = function() {
  can = document.getElementById("can"); ctx = can.getContext("2d");
 can.width = w = window.innerWidth; can.height = h =     window.innerHeight;
  var amount = 3;
  
  for(var i = 1; i < amount+1; i++) {
  var x = w/(amount+1)*i, y = h/5, width = height = Math.round(Math.random()*w/Math.pow(amount, 2)+25);

  boxes.push(new Box(x, y, width, height));
  }
  
  console.log("White : Black : Anomaly");
 console.log(" ");
 console.log("-------Circles-------");
 console.log(" ");



 var formatPercent = function(x) {
  var precision = 3;
  var dec = Math.pow(10, precision);
  var r = Math.round(x*dec)/dec * 100;
  var s = r.toPrecision(precision)+"%";
  return s;
 };


 boxes.forEach(function(ob) {
  ob.draw();
  var result = getRatios(getPixels(ob));
  console.log(formatPercent(result[0]) + " : " + formatPercent(result[1]) + " : " + formatPercent(result[2]));
 });
}

function getPixels(ob) {
 var imageData = ctx.getImageData(ob.x, ob.y, ob.width, ob.height)
 return imageData.data;
}

function getRatios(data) {
 var hitWhite = 0, hitBlack = 0, hitAnomaly = 0;
 var reps = Math.round(data.length/4/10);

 for(var i = 0; i < reps; i++) {
  var pixel = Math.round(Math.random()*data.length/4);
  var r = data[pixel*4], g = data[pixel*4+1], b = data[pixel*4+2];

  if(r == 255 & g == 255 & b == 255) { hitWhite++; }
  else if(r == 0 & g == 0 & b == 0) { hitBlack++; }
  else { hitAnomaly++; }
 }

 return [hitWhite/reps, hitBlack/reps, hitAnomaly/reps];
}

class Box {
 constructor(x, y, width, height) {
  this.x = x; this.y = y; this.width = width; this.height = height;
  this.cx = this.x+this.width/2; this.cy = this.y+this.height/2; this.r = this.width/2; this.angle = Math.PI*2;
 }

 draw() {
  ctx.fillStyle = "white";
  ctx.fillRect(this.x, this.y, this.width, this.height)

  ctx.fillStyle = "black";
  ctx.beginPath()
   ctx.arc(this.cx, this.cy, this.r, 0, this.angle, true)
   ctx.fill();
  ctx.closePath()
 }
}
canvas { background: rgb(50, 50, 50); }
<canvas id="can"></canvas>
SergGr
  • 23,570
  • 2
  • 30
  • 51