Class: FixedPnt
- Inherits:
-
Object
- Object
- FixedPnt
- Defined in:
- lib/fixedpnt.rb
Overview
DESCRIPTION
Binary Fixed Point calculations with Ruby.
This code is deeply inspired by:
-
Phil Tomson: fixedpt.rb: rubyforge.org/projects/fixedpt/
Thank you to Brian Candler for giving me very helpfull tips: www.ruby-forum.com/topic/4408936#new If you want to know what fixed point numbers are, you can read this: en.wikipedia.org/wiki/Fixed-point_arithmetic
FEATURES/PROBLEMS:
GOALS:
-
Simulating fixed point calculations done in hardware
-
Relatively fast (pure Ruby, for I don’t know C…)
-
The fixed point format of the result of an expression shall be the same as with Matlab. Example: Lets x and y each be 4 bits long with the binary point at one place from right. With z = x * y, z will have a length of 8 bits with the binary point at two places from right.
IT HAS:
-
tracking (at the very end you can read the min and max value a variable was assigned to)
-
“Inheritance” of fixed-point format (“right” format will be calculated automatically);
-
Overflow check;
IT HAS NOT:
-
OVERFLOW: NO overflow handler; instead: raises error;
-
NO COERCE;
-
Syntax not Matlab-compatible;
-
no rounding;
-
SIGNED only.
-
As of now, NO DIVISION (I did not need it)
KNOWN ISSUES:
-
Exception raising on overflow isn’t well done.
SYNOPSIS:
EXAMPLE USAGE:
require 'fixedpnt'
include FixedPntModule
$fixedpnt_track_min_max = true
a = fp(64, 8)
b = fp(64, 8)
c = fp
10000.times do |i|
a.assign( i )
b.assign( 2.2 * i )
if i > 5000
c.is a - b
else
c.is a + b
end
end
puts "a.abs_min_max = " + a.abs_min_max.inspect #=> [0.0, 99999.0]
puts "b.abs_min_max = " + b.abs_min_max.inspect #=> [0.0, 219997.796875]
puts "c.abs_min_max = " + c.abs_min_max.inspect #=> [-119998.796875, 160000.0]
puts "c.format = " + c.format.inspect #=> [65, 57, 8]
p required_fp_format(0.001, 220000) #=> [30, 10]
GENERAL USAGE:
CREATION: FixedPnt.new(total_bits=nil, frac_width=nil, value=nil)
fp1 = FixedPnt.new(32,16, 1.234)
* Sets fixed-point format to:
** total number of bits, including sign = 32;
** number of fractional bits = 16;
(Total number of bits may be smaller than number of fractional bits.)
* Sets value to 1.234;
fp1 = FixedPnt.new(32,16)
* Sets fixed-point format;
* Value is assigned later;
fp1 = FixedPnt.new()
* Fixed-point format and value are assigned later;
* Usefull for format-"inheritance"
* Usefull for min-max-tracking
SHORTCUT to FixedPnt.new(…):
include FixedPntModule
fp(...)
ASSIGNMENT ( # # # IMPORTANT !! # # # )
“fp1 = …”:
Use this in rare cases only!
("=": Assignment of variable name to object.)
instead, use (for speed and tracking):
“fp2.is(fp1)”:
Sets value (and format) of fp2 to equal fp1.
fp2 and fp1 must have same format, OR:
fp2's format is automatically taken from fp1
("inherited"), OR raises error if formats are different.
“fp3.is(fp1 + fp2”):
fp3 and "fp1 + fp2" must have same format, OR:
fp3's format is automatically taken from "fp1 + fp2"
("inherited"), OR raises error if formats are different.
"fp1.assign( a_float)" or "fp1.assign( an_integer )":
Assignes value to fp1.
“fp2.fit(fp1)”:
Use this to reformat ("resize") fp1.
MEMORIZE: “is”: left and right side are FixedPnt, formats are equal or automtically set; “fit”: left and right side are FixedPnt, formats are unequal; “assign”: left is FixedPnt, right is Numeric;
MIN-MAX-TRACKING:
Stores min and max value ever assigned to this fixed-point instance.
Tracking can be disabled by setting:
$fixedpnt_track_min_max = false ;
REQUIREMENTS:
-
Ruby 1.8.7 or higher
(I used it with Ruby 1.8.7 and 1.9.3.)
INSTALL:
-
sudo gem install fixedpnt
LICENSE:
(The MIT License)
Copyright © 2013 Axel Friedrich and contributors (see the CONTRIBUTORS file)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FOR INFORMATION:
Privat methods: overflow?:
checks for overflow;
equal_formats?:
checks for equal fixed-point formats (not an explicit method);
track_min_max__:
stores min and max value ever assigned to this fixed-point instance;
limits!:
sets the upper and lower assignable value for this fixed-point instance;
|_. method |_. overflow? |_. equal_formats? |_. track_min_max__ |_. limits! |_. Note | | new() | N(o) | N | N | N | (1) | | new(tw,fw) | N(o) | N | N | N | (1) | | new(tw,fw,val) | Y(es) | N | Y | Y | (1) | | .assign | Y | N | Y | Y (once) | | | .is | N |Y or assign format | Y | N | | | = | N | N | N | N |assignment to varname | | | | | | | |
(1): “new” sets:
* @stored_int = nil, if no value given;
* @bits, @frac_width, @int_width if FixedPnt.new(fixnum, fixnum)
* @bits=@frac_width=@int_width=nil if FixedPnt.new(nil, nil) # for format-inheritance
Constant Summary collapse
- VERSION =
"0.0.2"
Instance Attribute Summary collapse
-
#bits ⇒ Object
readonly
Returns the value of attribute bits.
-
#frac_width ⇒ Object
readonly
Returns the value of attribute frac_width.
-
#int_width ⇒ Object
readonly
Returns the value of attribute int_width.
-
#max_assigned_int ⇒ Object
Returns the value of attribute max_assigned_int.
-
#min_assigned_int ⇒ Object
Returns the value of attribute min_assigned_int.
-
#stored_int ⇒ Object
Returns the value of attribute stored_int.
Instance Method Summary collapse
-
#*(other) ⇒ Object
Matlab says for signed fixed-points: For c = a * b: c.int_width = a.int_width + b.int_width # IMHO, a.int_width + b.int_width - 1 would be sufficient c.frac_width = a.frac_width + b.frac_width.
-
#+(other) ⇒ Object
+ int_width = [self.int_width, other.int_width].max + 1 frac_width = [self.frac_width, other.frac_width].max overflow_handler = self.overflow_handler.
-
#-(other) ⇒ Object
-.
-
#-@ ⇒ Object
Unary operator “-”.
-
#abs_min_max ⇒ Object
Returns [min_assigned_value, max_assigned_value], where: min_assigned_value: min value, which ever was tried to be assigned to this fixed-point instance.
-
#assign(assigned_value) ⇒ Object
(also: #[]=)
Assign a Float or Integer value.
-
#fit(fixed_point) ⇒ Object
Convert fixed_point into another fixed-point format (“resize”).
-
#format ⇒ Object
Returns the actual Fixed Point format as [total_number_of_bits, int_width_including_sign, frac_width] .
-
#initialize(total_bits = nil, frac_width = nil, value = nil) ⇒ FixedPnt
constructor
“a = FixedPnt.new” returns fixed-point object with unset format.
-
#is(other) ⇒ Object
other: fixed_point of same format Assignes other to self.
-
#limits ⇒ Object
Returns min and max assignable values as Array.
-
#relative_min_max ⇒ Object
Returns [min_assigned_value, max_assigned_value], where: min_assigned_value: min value, which ever was tried to be assigned to this fixed-point instance, divided by min allowed value for this fixed-point format.
-
#to_bin ⇒ Object
Returns the stored integer of the fixed point value.
-
#to_binary ⇒ Object
Returns the binary representation of the stored integer with the virtual binary point inserted.
- #to_f ⇒ Object
- #to_i ⇒ Object
-
#to_int ⇒ Object
to_i.
-
#track_min_max__ ⇒ Object
for internal use only!.
Constructor Details
#initialize(total_bits = nil, frac_width = nil, value = nil) ⇒ FixedPnt
“a = FixedPnt.new” returns fixed-point object with unset format. This is usefull for automatic assigning format; example: a = FixedPnt.new b = FixedPnt.new(16,8, 1.23) a = b + b # a’s format will be set automatically
205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/fixedpnt.rb', line 205 def initialize(total_bits=nil, frac_width=nil, value=nil ) @bits = total_bits # total number of bits @frac_width = frac_width @stored_int = nil @max_stored_int = nil # max allowed value of the stored integer for the given format. @min_stored_int = nil # min allowed value of the stored integer for the given format. @int_width = @bits - @frac_width if @frac_width # Width of the integer part ("part left of binary point") of the stored integer incl. 1 Bit for sign @min_assigned_int = nil # min value (as stored integer), which was tried to be assigned to this fixed-point. TODO: Replace 999999999. @max_assigned_int = nil # max value (as stored integer), which was tried to be assigned to this fixed-point. TODO: Replace 999999999. self.assign(value) if value end |
Instance Attribute Details
#bits ⇒ Object (readonly)
Returns the value of attribute bits.
195 196 197 |
# File 'lib/fixedpnt.rb', line 195 def bits @bits end |
#frac_width ⇒ Object (readonly)
Returns the value of attribute frac_width.
195 196 197 |
# File 'lib/fixedpnt.rb', line 195 def frac_width @frac_width end |
#int_width ⇒ Object (readonly)
Returns the value of attribute int_width.
195 196 197 |
# File 'lib/fixedpnt.rb', line 195 def int_width @int_width end |
#max_assigned_int ⇒ Object
Returns the value of attribute max_assigned_int.
196 197 198 |
# File 'lib/fixedpnt.rb', line 196 def max_assigned_int @max_assigned_int end |
#min_assigned_int ⇒ Object
Returns the value of attribute min_assigned_int.
196 197 198 |
# File 'lib/fixedpnt.rb', line 196 def min_assigned_int @min_assigned_int end |
#stored_int ⇒ Object
Returns the value of attribute stored_int.
196 197 198 |
# File 'lib/fixedpnt.rb', line 196 def stored_int @stored_int end |
Instance Method Details
#*(other) ⇒ Object
Matlab says for signed fixed-points:
For c = a * b:
c.int_width = a.int_width + b.int_width # IMHO, a.int_width + b.int_width - 1 would be sufficient
c.frac_width = a.frac_width + b.frac_width
390 391 392 393 394 395 396 397 398 |
# File 'lib/fixedpnt.rb', line 390 def *(other) int_width = @int_width + other.int_width # IMHO, a.int_width + b.int_width - 1 would be sufficient frac_width = @frac_width + other.frac_width bits = int_width + frac_width res = FixedPnt.new(bits, frac_width) res.stored_int = self.stored_int * other.stored_int res end |
#+(other) ⇒ Object
+
int_width = [self.int_width, other.int_width].max + 1 frac_width = [self.frac_width, other.frac_width].max overflow_handler = self.overflow_handler
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/fixedpnt.rb', line 288 def +(other) # c = a + b -> c.bits = [a.bits, b.bits].max + 1 (like Matlab) # TODO: Can speed be improved? sif = self.int_width oif = other.int_width if oif > sif int_width = oif + 1 else int_width = sif + 1 end sfw = self.frac_width ofw = other.frac_width if ofw > sfw frac_width = ofw else frac_width = sfw end # int_width = [self.int_width, other.int_width].max + 1 # slower # frac_width = [self.frac_width, other.frac_width].max # slower bits = int_width + frac_width s = @stored_int o = other.stored_int frac_diff = @frac_width - other.frac_width if frac_diff > 0 o = o << frac_diff else s = s << -frac_diff end res = FixedPnt.new(bits, frac_width) res.stored_int = s + o res end |
#-(other) ⇒ Object
-
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/fixedpnt.rb', line 345 def -(other) # TODO: Can speed be improved? sif = self.int_width oif = other.int_width if oif > sif int_width = oif + 1 else int_width = sif + 1 end sfw = self.frac_width ofw = other.frac_width if ofw > sfw frac_width = ofw else frac_width = sfw end # int_width = [self.int_width, other.int_width].max + 1 # slower # frac_width = [self.frac_width, other.frac_width].max # slower bits = int_width + frac_width s = @stored_int o = other.stored_int frac_diff = @frac_width - other.frac_width if frac_diff > 0 o = o << frac_diff else s = s << -frac_diff end res = FixedPnt.new(bits, frac_width) res.stored_int = s - o res end |
#-@ ⇒ Object
Unary operator “-”
330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/fixedpnt.rb', line 330 def -@ if @stored_int == @min_stored_int raise "@stored_int must not equal @min_stored_int!" end res = self.dup # TODO: faster way? res.stored_int = - res.stored_int res.min_assigned_int = 999999999 res.max_assigned_int = -999999999 ##/ res.track_min_max__ if $fixedpnt_track_min_max res end |
#abs_min_max ⇒ Object
Returns [min_assigned_value, max_assigned_value], where: min_assigned_value: min value, which ever was tried to be assigned to this fixed-point instance. max_assigned_value: max value, which ever was tried to be assigned to this fixed-point instance.
490 491 492 493 494 495 |
# File 'lib/fixedpnt.rb', line 490 def abs_min_max( ) mi = @min_assigned_int * 1.0 / (1 << @frac_width) ma = @max_assigned_int * 1.0 / (1 << @frac_width) [ mi, ma ] end |
#assign(assigned_value) ⇒ Object Also known as: []=
Assign a Float or Integer value
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/fixedpnt.rb', line 221 def assign( assigned_value ) @assigned_value = assigned_value # TODO: Improve speed? case @assigned_value when Float limits! unless @max_stored_int @stored_int = (@assigned_value * 2**@frac_width).to_i overflow? when Fixnum limits! unless @max_stored_int @stored_int = @assigned_value << @frac_width overflow? else raise end track_min_max__ if $fixedpnt_track_min_max self end |
#fit(fixed_point) ⇒ Object
Convert fixed_point into another fixed-point format (“resize”).
246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/fixedpnt.rb', line 246 def fit( fixed_point ) @assigned_value = fixed_point limits! unless @max_stored_int @stored_int = @assigned_value.stored_int << (@frac_width - @assigned_value.frac_width ) overflow? ## if @int_width < @assigned_value.int_width track_min_max__ if $fixedpnt_track_min_max self end |
#format ⇒ Object
Returns the actual Fixed Point format as
- total_number_of_bits, int_width_including_sign, frac_width
-
.
Actually without the signed/unsigned flag
454 455 456 |
# File 'lib/fixedpnt.rb', line 454 def format( ) [ @bits, @int_width, @frac_width] end |
#is(other) ⇒ Object
other: fixed_point of same format Assignes other to self. Advantage over a simple “=”: tracking possible
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/fixedpnt.rb', line 263 def is( other ) @assigned_value = other unless @frac_width # TODO: Using Nil-class and mutate class possible? (-> a.=_... with a == nil ) @bits = other.bits @int_width = other.int_width @frac_width = other.frac_width else if @bits != other.bits || @frac_width != other.frac_width raise "!!!! ERROR: Fixed-point formats must equal, but self.format=#{ self.format.inspect } and other.format = #{ other.format.inspect }!" end end @stored_int = other.stored_int track_min_max__ if $fixedpnt_track_min_max self end |
#limits ⇒ Object
Returns min and max assignable values as Array.
475 476 477 478 479 480 481 |
# File 'lib/fixedpnt.rb', line 475 def limits( ) limits! mi = @min_stored_int * 1.0 / (1 << @frac_width) ma = @max_stored_int * 1.0 / (1 << @frac_width) [ mi, ma ] end |
#relative_min_max ⇒ Object
Returns [min_assigned_value, max_assigned_value], where: min_assigned_value: min value, which ever was tried to be assigned to this fixed-point instance, divided by min allowed value for this fixed-point format. max_assigned_value: max value, which ever was tried to be assigned to this fixed-point instance, divided by max allowed value for this fixed-point format.
505 506 507 508 |
# File 'lib/fixedpnt.rb', line 505 def relative_min_max( ) limits! [ @min_assigned_int.to_f/@min_stored_int.to_f, @max_assigned_int.to_f/@max_stored_int.to_f ] end |
#to_bin ⇒ Object
Returns the stored integer of the fixed point value. THE FIRST BIT IS FOR SIGN! Returns: String; Liefert die Integer-Darstellung (ohne virtuellen Punkt) TODO: Improve?
420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/fixedpnt.rb', line 420 def to_bin return nil unless @stored_int #str = sprintf("%0#{@bits}b",@ival) str = if @stored_int < 0 sprintf("%0#{@bits}b",2**@bits + @stored_int) else sprintf("%0#{@bits}b",@stored_int) end return str end |
#to_binary ⇒ Object
Returns the binary representation of the stored integer with the virtual binary point inserted. THE FIRST BIT IS FOR SIGN! TODO: Improve?
436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'lib/fixedpnt.rb', line 436 def to_binary return nil unless @stored_int str = self.to_bin values = str.split('') tmp = @bits-@frac_width if tmp >= 0 values.insert(@bits-@frac_width,".") else values.insert(0, "x.#{ 'x' * tmp.abs }") end values.join('') end |
#to_f ⇒ Object
410 411 412 413 |
# File 'lib/fixedpnt.rb', line 410 def to_f( ) # Thanks to Brian Candler. self.stored_int * 1.0 / (1 << @frac_width) end |
#to_i ⇒ Object
400 401 402 403 404 |
# File 'lib/fixedpnt.rb', line 400 def to_i( ) res = (@stored_int >> @frac_width) res = res + 1 if @stored_int < 0 res end |
#to_int ⇒ Object
to_i
406 407 408 |
# File 'lib/fixedpnt.rb', line 406 def to_int to_i end |
#track_min_max__ ⇒ Object
for internal use only!
459 460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/fixedpnt.rb', line 459 def track_min_max__( ) # for internal use only! if !@min_assigned_int || @stored_int < @min_assigned_int @min_assigned_int = @stored_int end if !@max_assigned_int || @stored_int > @max_assigned_int @max_assigned_int = @stored_int end ## @min_assigned_int = [@stored_int, @min_assigned_int].min # Probably slower ## @max_assigned_int = [@stored_int, @max_assigned_int].max # Probably slower nil end |