This is a little bit late (Wednesday), and doesn't behave as I
originally thought it ought to behave, because I don't have time to do
the different-operations-choose-types-differently properly. (the basic
idea is to coerce Integers to a class that wraps an Integer and does
coercions in different ways when presented with different operations)
Like most others, I implemented each differently sized FWI as a
separate class. In my code, one creates a new value by doing:
u_int_eight = FWI.new(32,false).new(8)
However, I also implement two other syntaxes (the syntax of the
original quiz spec and the syntax of Sander Land's test case) for
convenience. However, the number of bits aren't confined to any fixed
set; instead, classes are created on the fly as needed. (After using
the class a bit in irb, ask for FWI.constants)
As I worked on this quiz, I got into the idea of implementing C
arithmetic as closely as possible. This is significantly trickier
than it might appear at first; consider this C program:
int main(int argc, char *argv)
{
unsigned short int u_four = 4;
short int four = 4;
long int l_neg_nine = -9;
printf("The division yields %ld\n", l_neg_nine / u_four);
printf("The modulus yields %ld\n", l_neg_nine % u_four);
printf("The all signed division yields %ld\n", l_neg_nine / four);
printf("The all signed modulus yields %ld\n", l_neg_nine % four);
}
What should it produce? Well, it turns out that even if you know the
size of "long" and "short" ints (32 and 16 bits on most modern
machines), the result depends on whether you're using a compiler that
follows ansi C numeric rules or the rules of K&R C. (The second two
lines are the same under both sets of rules, but the first two lines
aren't)
Even then, there's also the issue that C and Ruby disagree on how
integer division and remainder should behave with negative numbers.
As everyone who's dealt with C much should know, -9/4 (assuming
everything is signed and long enough) is -2. However, in Ruby:
irb(main):001:0> -9 / 4
=> -3
That is, C produces (a*1.0/b).truncate whereas Ruby produces
(a*1.0/b).floor - I'll note that Python agrees with Ruby in this.
Perl simply promotes integers to floats when the division isn't
exact.
The definition of % in both C and Ruby is consistent with the
definition of integer /; this means that in C, -9 % 4 is -1 but in
Ruby -9 % 4 is 3. Note that I think that the Ruby behavior makes much
more sense, mathematically, and both Python and Perl agree with this
behavior. Still, it makes emulating C arithmetic (say, if one were
copying an algorithm from ancient source code) tricky.
My solution was to add two new methods to Integer: c_div and
c_modulo. These implementinteger division and integer mod with C
semantics. I also then added a class method called c_math that takes
three arguments (two operands and an operation) and does the
equivalent of statements like this:
int c = a / b;
Including promoting a and b to a common type and doing the
cast-to-signed-int if necessary. This method uses c_div and c_modulo
when appropriate.
Thinking of other uses of this FWI class, specifically of how it could
be useful in translating algorithms, led me to add a few x86 assembly
language operations. After all, fixed width integers are all over the
place in assembly, and it's perfectly conceivable that someone might
want to translate an algorithm from ancient 8086 assembly into ruby
for a modern machine. In this version, I've only added one assembly
operation (rcl - rotate left through carry) since the demonstrations
of the other operations weren't very interesting after all.
So here's the code. First, the file fwi_use.rb that demonstrates some
uses of the class: it reimplements the C program above in both K&R
mode and ansi mode, and then implements the CRC algorithm used in
xmodem (adapted from assembly code).
======================= fwi_use.rb ===========================
# The following C program was typed into t.c and then
# compiled with gcc in K&R mode, and gcc in ansi mode.
# The results are below.
···
#
# int main(int argc, char *argv)
# {
# unsigned short int u_four = 4;
# short int four = 4;
# long int l_neg_nine = -9;
#
# printf("The division yields %ld\n", l_neg_nine / u_four);
# printf("The modulus yields %ld\n", l_neg_nine % u_four);
#
# printf("The all signed division yields %ld\n", l_neg_nine / four);
# printf("The all signed modulus yields %ld\n", l_neg_nine % four);
# }
#
# esau:~$ gcc-2.95 -traditional -o t t.c && ./t
# The division yields 1073741821
# The modulus yields 3
# The all signed division yields -2
# The all signed modulus yields -1
# esau:~$ gcc-2.95 -ansi -o t t.c && ./t
# The division yields -2
# The modulus yields -1
# The all signed division yields -2
# The all signed modulus yields -1
require 'fwi'
u_four = FWI.new(16,false).new(4)
four = FWI.new(16,true).new(4)
l_neg_nine = FWI.new(32,true).new(-9)
FWI.new(32).set_coerce_method(:kr)
puts "K&R Math:"
print("The division yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :/, u_four)])
print("The modulus yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :%, u_four)])
print("The all signed division yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :/, four)])
print("The all signed modulus yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :%, four)])
FWI.new(32).set_coerce_method(:ansi)
puts "\nansi Math:"
print("The division yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :/, u_four)])
print("The modulus yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :%, u_four)])
print("The all signed division yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :/, four)])
print("The all signed modulus yields %d\n" %
[FWI.new(32).c_math(l_neg_nine, :%, four)])
FWI.new(32).set_coerce_method(:first)
puts "\nRuby Math:"
print("The division yields %d\n" %
[l_neg_nine / u_four])
print("The modulus yields %d\n" %
[l_neg_nine % u_four])
print("The all signed division yields %d\n" %
[l_neg_nine / four])
print("The all signed modulus yields %d\n" %
[l_neg_nine % four])
# CRC test
# adapted from the assembly-language routines at
# http://www.wps.com/FidoNet/source/DOS-C-sources/Old%20DOS%20C%20library%20source/crc.asm
def xmodem_crc(a,prev_crc=FWI.new(16,false).new(0))
crc = prev_crc
a.each_byte{ |al_val|
al = FWI.new(8,false).new(al_val)
8.times {
al.rcl!(1)
crc.rcl!(1)
crc ^= 0x1021 if FWI::FWIBase.carry>
}
}
crc
end
def add_crc(s)
a = s + "\x00\x00"
crc = xmodem_crc(a)
a[-2] = crc.to_i >> 8
a[-1] = crc.to_i & 0xFF
a
end
def check_crc(s)
xmodem_crc(s) == 0
end
puts "\nAdding a crc to a classic string:"
a = add_crc('Hello World!')
p a
puts "Modifying:"
a[1] = ?i
p a
puts "check_crc yields: " + check_crc(a).inspect
a[1] = ?e
p a
puts "check_crc yields: " + check_crc(a).inspect
__END__
================ fwi.rb ======================
class Integer
def c_modulo(o)
if ((self >= 0 and o >= 0) or (self <=0 and o < 0))
return self % o
else
return 0 if (self % o) == 0
return (self % o) - o
end
end
def c_div(o)
return((self - self.c_modulo(o))/o);
end
end
class FWI
class FWIBase
attr_reader :rawval
include Comparable
@@carry = 0
def initialize(n)
@rawval = n.to_i & maskval
@@carry = (n.to_i != to_i) ? 1 : 0
end
def FWIBase.carry; @@carry; end
def FWIBase.carry?; @@carry==1; end
def maskval; 0xF; end
def nbits; 4; end
def signed?; false; end
def to_i; rawval; end
def to_s; to_i.to_s; end
#def inspect; "<0x%0#{(nbits/4.0).ceil}x;%d>" % [rawval,to_i]; end
def inspect; to_s; end
def hash; to_i.hash; end
def coerce(o)
return self.class.fwi_coerce(o,self) if o.is_a? Integer
to_i.coerce(o)
end
def ==(o)
if (o.is_a? FWIBase) then
to_i == o.to_i
else
to_i == o
end
end
def eql?(o)
o.class == self.class and self == o
end
def FWIBase.set_coerce_method(meth)
case meth
when :kr
class << self
def fwi_coerce(a,b)
c = FWI.kr_math_class(a,b)
[c.new(a),c.new(b)]
end
end
when :ansi
class << self
def fwi_coerce(a,b)
c = FWI.ansi_math_class(a,b)
[c.new(a),c.new(b)]
end
end
when :first
class << self
def fwi_coerce(a,b)
return [a,b.to_i] if a.is_a? Integer
[a,a.class.new(b)]
end
end
when :second
class << self
def fwi_coerce(a,b)
return [a.to_i,b] if b.is_a? Integer
[b.class.new(a),b]
end
end
else
class << self
self.send(:undef_method,:fwi_coerce)
end
end
end
%w(+ - / * ^ & | ** % << >> c_div c_modulo div modulo).each { |op|
ops = op.to_sym
FWIBase.send(:define_method,ops) { |o|
if (o.class == self.class)
self.class.new(to_i.send(ops,o.to_i))
elsif o.is_a? Integer or o.is_a? FWIBase then
b = self.class.fwi_coerce(self,o)
b[0].send(ops,b[1])
else
to_i.send(ops,o)
end
}
}
%w(-@ +@ ~).each { |op|
ops = op.to_sym
FWIBase.send(:define_method,ops) {
self.class.new(to_i.send(ops))
}
}
def <=>(o)
to_i.<=>(o)
end
# And now add a few x86 assembly operations
# I only bother with rcl here, but for completeness
# one could easily implement rcr, rol, ror, adc, sbb, etc.
def rcl(n=1)
lbits = n % (nbits+1)
big = @rawval << lbits
big |= (FWIBase.carry << (lbits-1))
self.class.new((big & (2*maskval+1)) | (big >> (nbits+1)))
end
def rcl!(n)
@rawval = maskval && rcl(n).to_i
end
def FWIBase.c_math(a, op, b)
op = :c_div if op == 
op = :c_modulo if op == :%
a,b = self.fwi_coerce(a,b)
self.new(a.send(op,b))
end
end
@@clazzhash = Hash.new {|h,k|
l1 = __LINE__
FWI.class_eval(%Q[
class FWI_#{k} < FWI::FWIBase
def initialize(n); super(n); end
def maskval; #{(1<<k) - 1};end
def signed?; false; end
def nbits; #{k}; end
end
class FWI_#{k}_S < FWI_#{k}
def signed?; true; end
def to_i
if rawval < #{1<<(k-1)} then
rawval
else
rawval - #{1<<k}
end
end
end
], __FILE__, l1+1)
h[k] = FWI.class_eval(%Q"[FWI_#{k},FWI_#{k}_S]",
__FILE__, __LINE__ - 1)
}
def FWI.new(n,signed=true)
@@clazzhash[n][signed ? 1 : 0]
end
# K&R-like
# First, promote both to the larger size.
# Promotions of smaller to larger preserve unsignedness
# Once promotions are done, result is unsigned if
# either input is unsigned
def FWI.kr_math_class(a,b)
return b.class if (a.is_a? Integer)
return a.class if (b.is_a? Integer)
nbits = a.nbits
nbits = b.nbits if b.nbits > nbits
signed = a.signed? && b.signed?
FWI.new(nbits,signed)
end
# ANSI C-like
# promotions of smaller to larger promote to signed
def FWI.ansi_math_class(a,b)
return b.class if (a.is_a? Integer)
return a.class if (b.is_a? Integer)
nbits = a.nbits
nbits = b.nbits if b.nbits > nbits
signed = true
signed &&= a.signed? if (a.nbits == nbits)
signed &&= b.signed? if (b.nbits == nbits)
FWI.new(nbits,signed)
end
FWIBase.set_coerce_method(:ansi)
end
# support the syntax from the quiz spec
class UnsignedFixedWidthInt
def UnsignedFixedWidthInt.new(width,val)
FWI.new(width,false).new(val)
end
end
class SignedFixedWidthInt
def SignedFixedWidthInt.new(width,val)
FWI.new(width,true).new(val)
end
end
# support the syntax in Sander Land's test case
def UnsignedFWI(width)
FWI.new(width,false)
end
def SignedFWI(width)
FWI.new(width,true)
end
__END__