Last active
December 26, 2015 22:59
-
-
Save chriskillpack/7227089 to your computer and use it in GitHub Desktop.
To start my exploration of IEEE754 (floating-point numbers) I decided to write a program that converts a decimal number into it's IEEE754 32-bit counterpart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Convert a decimal number into its 32-bit IEEE754 floating-point | |
# representation. | |
# Example: | |
# $ ./floating-point.rb -1313.3125 | |
# Decimal: -1313.3125 | |
# As binary fraction: 010100100001.0101 | |
# Exponent = ^10 = 1024 = 10001001 | |
# Mantissa = 01001000010101000000000 | |
# 11000100101001000010101000000000 | |
# seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm | |
# 0xC4A42A00 | |
# | |
# When cross-checked with the output of the truth program for 39887.562 the | |
# results differ: 0x471BCF8F versus 0x471BCF90 (truth). Need to investigate but | |
# I suspect rounding modes are affecting fraction computation. (e.g. Nearest vs | |
# Zero). | |
MANTISSA_DIGITS = 23 | |
def number_components(number) | |
[number.to_i, number.modulo(1.0)] | |
end | |
def integral_to_binary(number) | |
# Work from LSB to MSB of the number | |
index = 0 | |
mask = nil | |
digits = [] | |
begin | |
mask = 1 << index | |
digits.push((number & mask) >> index) | |
index += 1 | |
end while mask <= number | |
digits.reverse.join('') | |
end | |
def fraction_to_binary_fraction(number) | |
# We know that the number is < 1 because it is the fractional component. If | |
# we repeatedly double the number and harvest the integer part of the result | |
# we can generate a sequence of 0's and 1's that is the binary fraction. | |
x = number | |
digits = [] | |
MANTISSA_DIGITS.times do |i| | |
x *= 2 | |
digits << x.to_i # Take the integer component | |
x = x.modulo(1.0) # Discard integer component | |
break if x.zero? # Unlikely to happen due to nature of the beast, but | |
# here for clean fractions. | |
end | |
digits.join('') | |
end | |
def normalize_parts(integral, fractional) | |
# Form is integral.fractional | |
# 1001101111001111.10010000000000000000000 | |
# IEEE Normalized Form: adjust exponent so that left-most 1 | |
# is only bit before decimal point. | |
# 1.00110111100111110010000000000000000000 | |
# This function does not return the leading 1 because it is implicit in the | |
# IEEE spec. | |
# This code isn't the best, I will rewrite when I can think of something | |
# more cleaner. | |
# Combine the two parts into a single representation with a decimal | |
# point. | |
combined = integral + '.' + fractional | |
# Find the left-most 1-bit. | |
left_most_one = (combined.index('1')) | |
return [-127, '0' * 23] unless left_most_one # Zero is a special case. | |
decimal_point = integral.length | |
# Exponent is the number of digits between the left-most 1-bit and the | |
# decimal point. | |
exponent = decimal_point - left_most_one - 1 | |
# Which side of the decimal point does the 1-bit lie? | |
if left_most_one < decimal_point | |
# Integral part. | |
# Take everything to the right of the 1-bit. | |
integral_remainder = integral[(left_most_one+1)..-1] | |
# Join it with the fractional part. | |
mantissa = integral_remainder + fractional | |
else | |
# Fractional part. | |
# We add one extra exponent power so the 1-bit crosses the decimal point. | |
exponent += 1 | |
# Take everything to the right of the 1-bit. | |
fractional_remainder = fractional[(left_most_one - decimal_point)..-1] | |
# By definition the integral part will be all 0-bits, so it can be ignored. | |
mantissa = fractional_remainder | |
end | |
# Trim mantissa to 23 bits and 0 pad short mantissas. | |
normalized_mantissa = mantissa[0...23].ljust(23, '0') | |
[exponent, normalized_mantissa] | |
end | |
if ARGV.length.zero? | |
puts "Usage: #{$0} number" | |
exit 1 | |
end | |
input_number = ARGV[0].to_f | |
puts "Decimal: #{input_number}" | |
integral, fraction = number_components(input_number.abs) | |
integral_bits = integral_to_binary(integral) | |
fraction_bits = fraction_to_binary_fraction(fraction) | |
puts "As binary fraction: #{integral_bits}.#{fraction_bits}" | |
# Determine sign bit | |
sign = (input_number >= 0) ? 0 : 1 | |
# Normalize integral and fractional parts | |
exponent, mantissa = normalize_parts(integral_bits, fraction_bits) | |
exponent_s = (exponent+127).to_s(2).rjust(8, '0') | |
print "Exponent = ^#{exponent} = #{2 ** exponent} = #{exponent_s}" | |
puts | |
puts "Mantissa = #{mantissa}" | |
ieee754 = sign.to_s + exponent_s + mantissa | |
puts ieee754 | |
puts 'seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm' | |
puts "0x#{ieee754.to_i(2).to_s(16).upcase}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The source of truth for floating point number representation. | |
// For a given decimal number, print out the IEEE754 number in | |
// hex and binary. | |
// | |
// Compile: | |
// $ g++ -Wall -std=c++11 -o truth floating-point.cc | |
// Example output: | |
// $ ./truth | |
// -1313.312500 | |
// 0xC4A42A00 | |
// 11000100101001000010101000000000 | |
// seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm | |
// sign = 1 | |
// exponent = 10001011 = ^10 = 1024 | |
// mantissa = 001001000010101000000000 | |
// 1.001001000010101000000000 x 2*10 | |
#include <stdio.h> | |
#include <cstdint> | |
// TODO: Take this as a command-line argument. | |
const float DECIMAL_NUMBER = -1313.3125; | |
// Print a number as binary. | |
// Taken from http://stackoverflow.com/a/3974138 with minor adaptions. | |
void printBits(size_t const size, void const * const ptr) { | |
uint8_t *b = (unsigned char*) ptr; | |
uint8_t byte; | |
int i, j; | |
for (i=size-1;i>=0;i--) | |
{ | |
for (j=7;j>=0;j--) | |
{ | |
byte = b[i] & (1<<j); | |
byte >>= j; | |
printf("%u", byte); | |
} | |
} | |
} | |
void destructure_IEEE754(float number, bool* negative, | |
uint32_t* exponent, uint32_t* mantissa) { | |
uint32_t num = *((uint32_t*)&number); | |
// Sign bit is bit 31. | |
*negative = (num & 0x80000000) >> 31; | |
// Exponent in bits 23-30. Subtract the exponent offset (+127). | |
*exponent = ((num >> 23) & 255) - 127; | |
// Mantissa in bits 0-23. | |
*mantissa = num & 0x7FFFFF; | |
} | |
int main(int argc, char** argv) { | |
printf("%f\n", DECIMAL_NUMBER); | |
unsigned int num = *((unsigned int*)&DECIMAL_NUMBER); | |
printf("0x%0X\n", num); | |
printBits(sizeof(num), &num); | |
puts(""); | |
printf("seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm\n"); | |
// Display the components of the number. | |
bool sign; | |
uint32_t exponent, exponent_offset, mantissa; | |
destructure_IEEE754(DECIMAL_NUMBER, &sign, &exponent, &mantissa); | |
printf("sign = %u\n", (int)sign); | |
printf("exponent = "); | |
exponent_offset = exponent - 127; | |
printBits(1, &exponent_offset); | |
printf(" = ^%d = %u", exponent, 1 << exponent); | |
puts(""); | |
printf("mantissa = "); | |
printBits(3, &mantissa); | |
puts(""); | |
// Display as scientific notation. | |
printf("1."); | |
printBits(3, &mantissa); | |
printf(" x 2*%d\n", exponent); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment