7

Say I have two values 0 <= a < b <= 1, how can I chose an x such that a <= x<b with the shortest binary expansion possible?

My approach so far is to take the binary strings of a and b, with the decimal point removed, and at the first place they differ, take the expansion of a up until that point. If there's more of a to consume, strip the last bit. Finally, add 1.

In JavaScript:

var binaryInInterval = function(a, b) {
  if (a < 0 || b > 1 || a >= b) return undefined;

  var i, u, v, x = '';

  a = a.toString(2).replace('.', '');
  b = b.toString(2).replace('.', '');

  for (i = 0; i < Math.max(a.length, b.length); i++) {
    u = parseInt(a.substr(i, 1), 10) || 0;
    v = parseInt(b.substr(i, 1), 10) || 0;

    x += u.toString();
    if (u != v) { 
      if (i + 1 < a.length) x = x.slice(0, -1);
      x += '1';
      break;
    }
  }

  return '0.' + x.substr(1);
};

This works, but I'm not convinced that it's generally correct. Any thoughts?...


EDIT I've already found a case that doesn't work correctly :P

binaryInInterval(0.25, 0.5) = '0.1'

0.25  0.01
0.5   0.1
        ^ Difference
          but a hasn't been fully consumed
          so we strip 00 to 0 before adding 1

EDIT 2 An alternative algorithm would be to iterate through 2^-n and check if any multiple of this fits within the interval. This would be more expensive, however.

4

3 回答 3

4

It will fail for inputs like a = 0.1, b = 0.100001 (in binary -- i.e. a = 0.5, b = 0.515625 in decimal). The correct answer would be 0.1 in this case, but your algorithm will produce 0.11 instead, which is not only not minimal-length, but greater than b :-(

Your digit-checking looks fine to me -- the problem is that when you have made the (right) decision to end the loop, you construct the wrong output if b's digit string is longer. One easy way to fix it would be to output digits one-at-a-time, as you go along: until you see a different character, you know you must include the current character.

One more tip: I don't know Javascript well, but I think both parseInt() calls are unnecessary, since nothing you do with u or v actually requires arithmetic.

[EDIT]

Here is some example digit-at-a-time code that incorporates a few other considerations:

var binaryInInterval = function(a, b) {
  if (a < 0 || b > 1 || a >= b) return undefined;

  if (a == 0) return '0';  // Special: only number that can end with a 0

  var i, j, u, v, x = '';

  a = a.toString(2).replace('.', '');
  b = b.toString(2).replace('.', '');

  for (i = 0; i < Math.min(a.length, b.length); i++) {
    u = a.substr(i, 1);
    v = b.substr(i, 1);

    if (u != v) {
      // We know that u must be '0' and v must be '1'.
      // We therefore also know that u must have at least 1 more '1' digit,
      // since you cannot have a '0' as the last digit.
      if (i < b.length - 1) {
        // b has more digits, meaning it must
        // have more '1' digits, meaning it must be larger than
        // x if we add a '1' here, so it's safe to do that and stop.
        x += '1';     // This is >= a, because we know u = '0'.
      } else {
        // To ensure x is >= a, we need to look for the first
        // '0' in a from this point on, change it to a '1',
        // and stop.  If a only contains '1's from here on out,
        // it suffices to copy them, and not bother appending a '1'.
        x += '0';
        for (j = i + 1; j < a.length; ++j) {
          if (a.substr(j, 1) == '0') {
            break;
          }
        }
      }
      break;  // We're done.  Fall through to fix the binary point.
    } else {
      x += u;    // Business as usual.
    }
  }

  // If we make it to here, it must be because either (1) we hit a
  // different digit, in which case we have prepared an x that is correct
  // except for the binary point, or (2) a and b agree on all
  // their leftmost min(len(a), len(b)) digits.  For (2), it must therefore be
  // that b has more digits (and thus more '1' digits), because if a
  // had more it would necessarily be larger than b, which is impossible.
  // In this case, x will simply be a.
  // So in both cases (1) and (2), all we need to do is fix the binary point.
  if (x.length > 1) x = '0.' + x.substr(1);
  return x;
};
于 2012-12-21T12:12:33.030 回答
3
function bin(a, b) {
 var den = 1;
 while (true) {
  var bint = Math.floor(b);
  if (bint == b) bint--;
  if (a <= bint) {
   return bint / den;
  }
  den *= 2;
  a *= 2;
  b *= 2;
 }
}

The code iterates through larger and larger power-of-two denominators (den) until it finds one that supports a value that fits in the range.

于 2012-12-22T11:29:58.827 回答
2

I am posting another version of the code. That tries to fix the known issues:

  var binaryInIntervalNew = function(inpA, inpB) {

    if (inpA < 0 || inpB > 1 || inpA >= inpB) return undefined;

    var i, u, v, x = '';

    a = inpA.toString(2).split('.');
    b = inpB.toString(2).split('.');

    a = a[a.length - 1];
    b = b[b.length - 1];

    for (i = 0; i < Math.min(a.length, b.length); i++) {

      u = a.substr(i, 1);
      v = b.substr(i, 1);

      if (u !== v) {
        // x cannot become equal to b, let us verify that
        if ((i+1) === b.length) {
          x += '01';
        } else {
          x += '1';
        }
        break;
      } else {
        x += u;
      }
      // console.log("x: " + x + " i:" + i);
    }

    x = '0.' + x;
    return parseFloat(x);
  };

A short function to test both functions:

function bin(a, b) {
  console.log("a:" + a.toString(2));
  console.log("b:" + b.toString(2));
  if (binaryInIntervalNew) console.log("binaryInIntervalNew: " + binaryInIntervalNew(a, b));
  if (binaryInInterval) console.log("binaryInInterval: " + binaryInInterval(a, b));
}

Now few results:

bin(1/16, 1/4);
a:0.0001 
b:0.01
binaryInIntervalNew: 0.001
binaryInInterval: 0.01

bin(.1, .2);
a:0.0001100110011001100110011001100110011001100110011001101 
b:0.001100110011001100110011001100110011001100110011001101
binaryInIntervalNew: 0.001
binaryInInterval: 0.001

bin(1/4, 1/2);
a:0.01 b:0.1
b:0.1
binaryInIntervalNew: 0.01
binaryInInterval: 0.1

bin(.2, .5);
a:0.001100110011001100110011001100110011001100110011001101 b:0.1
b:0.1
binaryInIntervalNew: 0.01
binaryInInterval: 0.1

bin(.0011, .1);
a:0.00000000010010000001011011110000000001101000110110111000101111 b:0.0001100110011001100110011001100110011001100110011001101
b:0.0001100110011001100110011001100110011001100110011001101
binaryInIntervalNew: 0.0001
binaryInInterval: 0.0001
于 2012-12-21T17:02:11.460 回答