DECTOBIN-function for decimal numbers >= 512 ?

  • Thread starter Thread starter Martin Saevik
  • Start date Start date
M

Martin Saevik

Anyone knows how to use this function for decimal numbers
from 512 and up ? I get the message #NUM! whene I try
coverting decimal numbers 512 and above. Thanks for any
help !
 
J.E. McGimpsey said:
....

UDFs should be avoided when built-in functions can be adapted without undue
formula length. For instance,

=DEC2BIN(INT(x/2^27),9)&DEC2BIN(INT(MOD(x,2^27)/2^18),9)
&DEC2BIN(INT(MOD(x,2^18)/2^9),9)&DEC2BIN(MOD(x,2^9),9)

This adds on a lot of leading zeros. To remove them, use

=MID(DEC2BIN(INT(x/2^27),9)&DEC2BIN(INT(MOD(x,2^27)/2^18),9)
&DEC2BIN(INT(MOD(x,2^18)/2^9),9)&DEC2BIN(MOD(x,2^9),9),
36-INT(LOG(x,2)),36)

We could argue whether this is undue formula length, but even with this many
built-in function calls, it may calculate faster than a udf.
 
In general, I agree with you Harlan. I wouldn't even call the second
formula of undue length. OTOH, I wrote the UDF to be somewhat more
flexible (and I rewrote it again this morning when I saw how awful
the code was - and I'm looking forward to further critiques). The
UDF allows for specifying bit length as an argument rather than
being hard coded, and it accepts negative numbers and zero.

The other advantage of the UDF is that it doesn't require the ATP to
be installed.

In a production environment with fixed or minimum bit length, your
formula wins hands down (except for the ATP part). In a
development/testing environment (which is what I originally wrote
the code for) I'll take the flexibility. I also like the clarity.
Six months after I last touch the project, I'll probably be able to
remember what LongDec2Bin(A1, 16) does much more easily than the
formula. And it's also handy to have around in an add-in of
utilities for the times I just need a one-off dec to binary.

Since the OP didn't specify his application, I hope he'll see your
reply. I'll also revise my web page to reflect your comments (and
reference this thread).

Thanks!
 
J.E. McGimpsey said:
In a production environment with fixed or minimum bit length, your
formula wins hands down (except for the ATP part). . . .

I meant to counter with

=TEXT(SUMPRODUCT(10^{7;6;5;4;3;2;1;0}
*INT(MOD(A1,2^{32;31;30;29;28;27;26;25})/2^{31;30;29;28;27;26;25;24})),
"00000000")&TEXT(SUMPRODUCT(10^{7;6;5;4;3;2;1;0}
*INT(MOD(A1,2^{24;23;22;21;20;19;18;17})/2^{23;22;21;20;19;18;17;16})),
"00000000")&TEXT(SUMPRODUCT(10^{7;6;5;4;3;2;1;0}
*INT(MOD(A1,2^{16;15;14;13;12;11;10;9})/2^{15;14;13;12;11;10;9;8})),
"00000000")&TEXT(SUMPRODUCT(10^{7;6;5;4;3;2;1;0}
*INT(MOD(A1,2^{8;7;6;5;4;3;2;1})/2^{7;6;5;4;3;2;1;0})),"00000000")

but the final MOD term errors out with my test value of 12345678000, which
it shouldn't.
 
...
...
. . . (and I rewrote it again this morning when I saw how awful
the code was - and I'm looking forward to further critiques). . . .
...

You asked for this!

Your latest UDF chokes on valid 32 longs such as 2^31-1 and -(2^31). I believe
the problem is in

nIn = -nIn - 1 'Twos-complement

which should be

nIn = -(nIn + 1)

(Ain't bundary values a pain?) The 3 nested .Substitute calls are inelegant.

So, if udfs are what you want, here's an alternative. Note: too small an
optional width results in a string of asterisks rather than an error value.


Function hgd2b(ByVal n As Long, Optional p As Long) As String
Dim k As Long, x As Double, neg As Boolean

If n < 0 Then
neg = True
n = -(n + 1)
End If

x = Log(Abs(n)) / Log(2)
k = Int(x) + IIf(Int(x) <> x, 1, 0) + IIf(neg, 1, 0)

If p <= 0 Then p = k

If p < k Then
hgd2b = String(p, "*")
Exit Function
End If

hgd2b = String(p, IIf(neg, "1", "0"))

If n = 0 Then Exit Function

For k = p To p - k Step -1
If n - 2 * Int(n \ 2) > 0 Then Mid(hgd2b, k, 1) = IIf(neg, "0", "1")
n = n \ 2
Next k

End Function
 
Harlan Grove said:
You asked for this!

Yes I did - thanks!
(Ain't bundary values a pain?)

Yup - especially when I tested them before I made one small
revision...

Actually, the problem was in Log(nIn + 1). nIn+1 implicitly coerces
the 1 to a Long which produces an overflow in the long result. I
corrected it using Long(nIn + 1#). Thanks!

I suppose it would be churlish to point out that your function
chokes on the "boundary condition" of zero, when it would be
trivially easy to fix...<g>

OTOH, it also chokes on positive powers of 2 if p is omitted - I
don't know whether that's easy or not. Probably.

And some negative values:

?hgd2b(-3)
01
?hgd2b(-3,2)
01
The 3 nested .Substitute calls are inelegant.

I wholly agree. I'll have to think about whether I like your method
better.
So, if udfs are what you want, here's an alternative. Note: too small an
optional width results in a string of asterisks rather than an error value.

This I don't like better, but I suspect it's a matter of taste.
Seems preferable to me to test with ISNA(), for example, than by a
string comparison. Again, however, trivial to implement.
Function hgd2b(ByVal n As Long, Optional p As Long) As String
Dim k As Long, x As Double, neg As Boolean

If n < 0 Then
neg = True
n = -(n + 1)
End If

x = Log(Abs(n)) / Log(2)

here's where n=0 fails. I think Abs is superfluous as well.
k = Int(x) + IIf(Int(x) <> x, 1, 0) + IIf(neg, 1, 0)

I've probably missed it, but is the overhead of the IIf() call
preferable to the implicit coercion of

k = INT(x) - (Int(x) <> x) - neg

?? I have an unreasonable dislike of IIf(), I suppose.

Note that for even powers of 2, this gives one two few digits.
If p <= 0 Then p = k

If p < k Then
hgd2b = String(p, "*")
Exit Function
End If

Personal preference: Exit Function seems inherently inelegant to me,
except just before an error handler. It seems tolerable if it's in
the first couple of lines, but any deeper in the procedure and I
prefer to use a If...Else...EndIf.
 
J.E. McGimpsey said:
Yes I did - thanks! ....
Actually, the problem was in Log(nIn + 1). nIn+1 implicitly coerces
the 1 to a Long which produces an overflow in the long result. I
corrected it using Long(nIn + 1#). Thanks!

nIn = -nIn - 1 is an error when nIn = -(2^31), but it only affects that one
value.
I suppose it would be churlish to point out that your function
chokes on the "boundary condition" of zero, when it would be
trivially easy to fix...<g>

Not churlish. I didn't test mine thoroughly. I was lazy and tried to make
the fewest possible modifications from your udf. That turned out to be
unwise.
I wholly agree. I'll have to think about whether I like your method
better.

All I'll claim for my approach is that I believe it deals more efficiently
with VBA strings by (1) initializing the return value using a String() call,
and then modifying default (all 0s for nonnegatives, all 1s for negatives)
bits as needed using Mid() _statement_ calls. That is, it avoids multiple
memory allocation calls that could arise from building up the return value
one digit at a time using string concatenation.
value.

This I don't like better, but I suspect it's a matter of taste.
Seems preferable to me to test with ISNA(), for example, than by a
string comparison. Again, however, trivial to implement.
....

This is a deeper point, not just subjective. After thinking about it,
returning "" when the specified width is less than the required width would
allow simple string comparison to check for errors, e.g., hgd2b(n,p)="". As
long as Excel maintains a ridiculously small number of allowed nested
function call levels, I'll try to avoid returning error values except for
functions that could return any real number or text value when there were no
errors. Only those require error values to indicate errors. Avoiding error
return values means one fewer level of nested function calls - often a very
good thing.
here's where n=0 fails. I think Abs is superfluous as well.

Correct on both points.
I've probably missed it, but is the overhead of the IIf() call
preferable to the implicit coercion of

k = INT(x) - (Int(x) <> x) - neg

?? I have an unreasonable dislike of IIf(), I suppose.

This is an interesting question. Personally, I dislike using implicit
coersion of booleans to integers in VBA because TRUE is -1 rather than +1.
Makes the code harder to understand. Nevertheless, implicit coersion is
almost certainly much faster than IIF calls. I use IIF because I'm addicted
to C/C++/awk ternary operator expressions. This is as much a matter of taste
as it is a matter of readability vs speed.
Note that for even powers of 2, this gives one two few digits.
Yup.


Personal preference: Exit Function seems inherently inelegant to me,
except just before an error handler. It seems tolerable if it's in
the first couple of lines, but any deeper in the procedure and I
prefer to use a If...Else...EndIf.

OK, this is a Pascal vs C argument in the making. There are arguments pro
and con, but I'm deep in the C camp with respect to the value of immediate
returns vs elegance. This particular problem may not require convoluted If
structures to replace the immediate return, but too many Ifs spoil the code.
There's a reason applications written in C and its offspring outnumber those
written in Pascal by a thousand-fold or more.

So time for my own rewrite.


Function hgd2b(ByVal n As Long, Optional p As Long) As String
Dim k As Long, neg As Boolean, sb As String * 1

If n < 0 Then
neg = True
n = -(n + 1) 'note: -1 becomes 0
k = 32 'to eliminate ambiguity
ElseIf n > 0 Then
k = Int(Log(n) / Log(2)) + 1
Else 'n = 0
k = 1
End If

If p <= 0 Then
p = k
ElseIf p < k Then
Exit Function 'returns "" when p too narrow
End If

If neg Then
hgd2b = String(p, "1") 'init as -1
sb = "0"
Else
hgd2b = String(p, "0") 'init as 0
sb = "1"
End If

If n = 0 Then Exit Function 'returns both 0 and -1 quickly

For k = p To p - k Step -1
If n - 2 * Int(n \ 2) > 0 Then Mid(hgd2b, k, 1) = sb
n = n \ 2
Next k

End Function


No IIF calls, no Excel Application method calls, many immediate returns (I
like 'em), one loop with no post processing. This time I tested it using


Sub foo()
Dim k As Long, n As Long, fd As Variant
fd = FreeFile
Open ENVIRON("TEMP") & "\exceld2b.txt" For Output As #fd
For k = -31 To 30
If k < 0 Then
n = -(2 ^ Abs(k))
Print #fd, n, hgd2b(n)
Print #fd, n + 1, hgd2b(n + 1)
Print #fd, n \ 2 - 1, hgd2b(n \ 2 - 1)
ElseIf k = 0 Then
Print #fd,
Print #fd, 0, hgd2b(0)
Print #fd, 1, hgd2b(1)
Print #fd,
Else
n = 2 ^ k
Print #fd, n, hgd2b(n)
Print #fd, n + 1, hgd2b(n + 1)
Print #fd, 2# * n - 1#, hgd2b(2# * n - 1#)
End If
Next k
Close #fd
End Sub


Everything looks good.

Note that negative values always show 32 binary digits. This is necessary to
eliminate ambiguity. IOW, IMO it's OK to assume implicit leading zeros, so
11B and 1111B are three and fifteen, respectively, rather than -1 to 2 and 4
bits, respectively. The alternative would be always to display a leading
zero for positive values and a leading 1 for negative values. But that leads
to certain absurdities such as one needing to be 01 while negative one could
be just 1.
 
Harlan Grove said:
All I'll claim for my approach is that I believe it deals more efficiently
with VBA strings by (1) initializing the return value using a String() call,
and then modifying default (all 0s for nonnegatives, all 1s for negatives)
bits as needed using Mid() _statement_ calls. That is, it avoids multiple
memory allocation calls that could arise from building up the return value
one digit at a time using string concatenation.

Good point - I certainly like reducing allocation calls.
This is a deeper point, not just subjective. After thinking about it,
returning "" when the specified width is less than the required width would
allow simple string comparison to check for errors, e.g., hgd2b(n,p)="". As
long as Excel maintains a ridiculously small number of allowed nested
function call levels, I'll try to avoid returning error values except for
functions that could return any real number or text value when there were no
errors. Only those require error values to indicate errors. Avoiding error
return values means one fewer level of nested function calls - often a very
good thing.

I certainly agree with you as to the efficacy of reducing function
calls, and that therefore it's not just a matter of taste. Choosing
to go with one method or the other should be a design decision. In
this case, I (reluctantly) come down on the side of similarity with
the DEC2BIN() function and XL's error standards.
This is an interesting question. Personally, I dislike using implicit
coersion of booleans to integers in VBA because TRUE is -1 rather than +1.
Makes the code harder to understand. Nevertheless, implicit coersion is
almost certainly much faster than IIF calls. I use IIF because I'm addicted
to C/C++/awk ternary operator expressions. This is as much a matter of taste
as it is a matter of readability vs speed.

I feel fine using implicit coercion, since TRUE= -1 has always
seemed more natural to me than TRUE=1 (a byproduct of programming
in assembler at an early age, I suspect).
OK, this is a Pascal vs C argument in the making. There are arguments pro
and con, but I'm deep in the C camp with respect to the value of immediate
returns vs elegance. This particular problem may not require convoluted If
structures to replace the immediate return, but too many Ifs spoil the code.
There's a reason applications written in C and its offspring outnumber those
written in Pascal by a thousand-fold or more.

Just don't lump me in the Pascal camp. While I had to use it for one
college course (and I took it at the same time as I was teaching
myself APL - talk about style differences!), I never really bought
into the absolute benefit of elegance over efficiency. Instead, the
debugging involved in my first large commercial coding job, which
involved modification of existing spaghetti code with over 800 subs,
convinced me of the benefit of reducing the number of exit points
for each sub. I still put in an Exit Sub/Function where it seems
appropriate, but I've found that the few nanoseconds it costs to
execute a branch can sometimes save a trillion times that in wasted
time in code maintenance (especially if someone else will have to
maintain my code), with no discernable delay to the user over his or
her lifetime.

OTOH, for me it's certainly context dependent. When inserting
in-line machine code to a C application, the whole point is speed,
so elegance be damned and full documentation be required.
So time for my own rewrite.

Thanks - I'll reference this on my site, and look at modifying my
routine where appropriate.
Note that negative values always show 32 binary digits. This is necessary to
eliminate ambiguity. IOW, IMO it's OK to assume implicit leading zeros, so
11B and 1111B are three and fifteen, respectively, rather than -1 to 2 and 4
bits, respectively. The alternative would be always to display a leading
zero for positive values and a leading 1 for negative values. But that leads
to certain absurdities such as one needing to be 01 while negative one could
be just 1.

I'll prefer to retain the flexibility. "Negative" binary values are
only returned if the input is negative, so there's no confusion to
me between returning %1111 as the decimal 15 and as the four digit
two's complement of decimal -1 (I deliberately used that isomorphism
many times early on.)

Besides, I'd hate to lock out a solution for when XL starts using
64-bit longs...<vbg>
 
...
...
Besides, I'd hate to lock out a solution for when XL starts using
64-bit longs...<vbg>

I should know better than to mix different threads, but . . . wanna bet MOD will
still be fubar for large quotients when Excel is ported to 64-bit CPUs?
 
Harlan Grove said:
I should know better than to mix different threads, but . . .
wanna bet MOD will still be fubar for large quotients when Excel
is ported to 64-bit CPUs?

No more than I'd want to bet that 64-bit XL will have more than 2^16
rows and 2^8 columns...
 
Back
Top