From a3d52c9028312706b45497c6b2205b8b165ef348 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 11 May 2015 20:13:16 -0700 Subject: 1349 - snapshot: floating-point support --- 021arithmetic.cc | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/021arithmetic.cc b/021arithmetic.cc index 307bcb18..d36b15f3 100644 --- a/021arithmetic.cc +++ b/021arithmetic.cc @@ -227,3 +227,142 @@ recipe main [ +mem: storing 2 in location 3 +run: product 1 is 4 +mem: storing 5 in location 4 + +//:: Support for non-integer numbers. + +//: Supporting non-integers is hopefully the only place where we need to think +//: about the size of each location of memory. +:(after "int main") +assert(sizeof(long long int) == 8); +assert(sizeof(double) == 8); + +:(before "End Globals") +//: Conventional hardware uses the most significant bit to represent the sign +//: in both (2's complement) integers, and so-called floating-point numbers +//: (with sign, exponent and fraction regions: https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +static const long long int HOST_SET_NEGATIVE = 0x8000000000000000LL; +//: Floating-point numbers store the sign of the exponent in the second-most +//: significant bit. +static const long long int HOST_SET_FLOAT_NEGATIVE_EXPONENT = 0x4000000000000000LL; + +//: As an experiment, we'd like to not have to distinguish between the two in +//: mu. So we'll use the most-significant bit to represent whether a number is +//: an integer (MSB 0) or a float (MSB 1). This will halve the set of numbers +//: we can represent, whether integers or non-integers, but that price seems +//: reasonable right now. +const long long int MU_NUMBER_TYPE_MASK = 0x8000000000000000LL; +//: As a result, the sign bit is now pushed to the second-most significant +//: bit.. +const long long int MU_NUMBER_SIGN_MASK = 0x4000000000000000LL; +//: ..and the sign of the exponent for floating-point is pushed to the +//: third-most significant bit. +const long long int MU_FLOAT_EXPONENT_SIGN_MASK = 0x2000000000000000LL; + +//: One nice side-effect of this mergek + +:(after "int main") +assert(MU_NUMBER_TYPE_MASK == HOST_SET_NEGATIVE); +assert(MU_NUMBER_SIGN_MASK == HOST_SET_FLOAT_NEGATIVE_EXPONENT); + +:(code) +inline bool is_float(long long int number) { + return number & MU_NUMBER_TYPE_MASK; +} + +inline bool is_integer(long long int number) { + return !is_float(number); +} + +// both floats and integers use the most significant bit for the sign +inline bool is_negative(long long int number) { + return number & MU_NUMBER_SIGN_MASK; +} + +inline bool float_has_negative_exponent(long long int number) { + return number & MU_FLOAT_EXPONENT_SIGN_MASK; +} + +long long int to_integer(long long int number) { + assert(is_integer(number)); + if (is_negative(number)) { + // slide the sign over by one bit + number = number | HOST_SET_NEGATIVE; + // clear the old sign bit + number = number & (~MU_NUMBER_SIGN_MASK); + } + return number; +} + +double to_float(long long int number) { + assert(is_float(number)); +//? cerr << "0: " << std::hex << number << std::dec << ' ' << *reinterpret_cast(&number) << '\n'; //? 1 + if (is_negative(number)) { + // slide the sign over by one bit + number = number | HOST_SET_NEGATIVE; +//? cerr << "1: " << std::hex << number << std::dec << ' ' << *reinterpret_cast(&number) << '\n'; //? 1 + // clear the old sign bit + number = number & (~MU_NUMBER_SIGN_MASK); +//? cerr << "2: " << std::hex << number << std::dec << ' ' << *reinterpret_cast(&number) << '\n'; //? 1 + } + if (float_has_negative_exponent(number)) { + // slide the sign of the exponent over by one bit + number = number | HOST_SET_FLOAT_NEGATIVE_EXPONENT; +//? cerr << "3: " << std::hex << number << std::dec << ' ' << *reinterpret_cast(&number) << '\n'; //? 1 +//? // clear the old exponent sign bit +//? number = number & (~MU_FLOAT_EXPONENT_SIGN_MASK); +//? cerr << "4: " << std::hex << number << std::dec << ' ' << *reinterpret_cast(&number) << '\n'; + } + double result = *reinterpret_cast(&number); + return result; +} + +void test_integer_representation() { + // Assuming long long int is 8 bytes: + static const long long int nbits = 64; +//? cerr << '\n'; //? 1 +//? cerr << nbits << " iterations\n"; //? 1 +//? cerr << std::hex; //? 1 +//? cerr << "type mask: " << MU_NUMBER_TYPE_MASK << '\n'; //? 1 +//? cerr << "sign mask: " << MU_NUMBER_SIGN_MASK << '\n'; //? 1 +//? cerr << std::dec; //? 1 + // until the last 2 bits all integers retain their value + for (int i = 0; i < nbits-2; ++i) { + long long int n = (0x1LL << i); +//? cerr << i << ": " << "0x" << std::hex << n << std::dec << ' ' << n << " => " << to_integer(n) << '\n'; //? 1 + CHECK(is_integer(n)); + CHECK(n == to_integer(n)); + } + long long int n = (0x1LL << (nbits-2)); + CHECK(is_integer(n)); +//? cerr << nbits-2 << ": " << "0x" << std::hex << n << std::dec << ' ' << n << " => " << to_integer(n) << '\n'; //? 1 + CHECK(is_negative(n)); +} + +void test_noninteger_representation() { + // Assuming long long int is 8 bytes: + static const long long int nbits = 64; + static const long long int FLOAT_MASK = (0x1LL << (nbits-1)); + double f = -2.0; + printf("0x%llx\n", *(long long int*)&f); + cout << '\n'; + for (int fraction = 0; fraction < 52; ++fraction) { + for (int exponent = 52; exponent < 63; ++exponent) { + long long int n = (0x1LL << fraction) | (0x1LL << exponent) | FLOAT_MASK; + CHECK(is_float(n)); + double result_on_host = *reinterpret_cast(&n); + double result = to_float(n); + printf("%02d %d: 0x%llx %.30e\n", fraction, exponent, n, result_on_host); + printf("=> %.30e\n", result); + } + } +//? long long int n = ((0x1LL << (nbits-2)) | FLOAT_MASK); +//? CHECK(is_float(n)); +//? double result_on_host = *reinterpret_cast(&n); +//? double result = to_float(n); +//? cout << nbits-2 << ": " << "0x" << std::hex << n << std::dec << ' ' << result_on_host +//? << " => " << result << '\n'; +} + +:(before "End Includes") +#include +#include -- cgit 1.4.1-2-gfad0