Skip to content

Instantly share code, notes, and snippets.

@michaeleisel
Created August 9, 2021 20:24
Show Gist options
  • Save michaeleisel/f7b6ece0587bf982895d1eb0bf2b2aa8 to your computer and use it in GitHub Desktop.
Save michaeleisel/f7b6ece0587bf982895d1eb0bf2b2aa8 to your computer and use it in GitHub Desktop.
void fallback(double d, char *buffer) {
snprintf(buffer, 25, "%0.16e", d);
}
void handleUncommonCases(double d, char *buffer) {
if (std::isinf(d)) {
if (d < 0) {
(*buffer++) = '-';
}
strcpy(buffer, "Infinity");
} else if (std::isnan(d)) {
strcpy(buffer, "NaN");
} else if (!std::isnormal(d)) {
// This logic is platform-specific anyways, so let the platform handle it
sprintf(buffer, "%0.17e", d);
}
}
// Note that NaN and infinity are not allowed in JSON
// todo: add subnormal/infinity tests, zero test for all forms, e.g. negative
// todo: is 17 digits still sufficient when we're rounding down and not to nearest?
void new_mult_style(double d, char *buffer) {
// Decompose double
uint64_t bits = 0;
memcpy(&bits, &d, sizeof(d));
uint64_t mantissa = (bits & ((~0ULL) >> 12)) | (1ULL << 52);
int exp = ((bits >> 52) & 0x7FF) - 1023;
// Handle uncommon cases
if (unlikely(exp == 1024 /* infinite or NaN */ || (exp == -1023 && mantissa != (1ULL << 52)/* subnormal */))) {
handleUncommonCases(d, buffer);
return;
}
// Handle if negative
bool isNegative = bits >> 63;
if (isNegative) {
buffer[0] = '-';
buffer++;
}
// Compute product
PowerConversion conversion = kPowerConversions[exp];
int16_t pow10 = conversion.power;
if (mantissa > conversion.mantissaCutoff) {
pow10--;
}
Pair pair = getPair(pow10);
int16_t power = pair.power;
power -= exp;
__uint128_t product128 = ((__uint128_t)pair.mantissa) * mantissa;
uint64_t product = (uint64_t)(product128 >> power);
if (unlikely(!(0 <= pow10 && pow10 < 26))) {
if (unlikely((product128 + mantissa) >> power != product)) {
fallback(d, buffer);
return;
}
}
// Write out digits
uint64_t first = product / 10000000000000000ULL;
product -= first * 10000000000000000ULL;
(*buffer++) = '0' + first;
(*buffer++) = '.';
uint32_t lo8 = (uint32_t)(product % 100000000);
uint32_t hi8 = (uint32_t)(product / 100000000);
uint16_t lolo4 = lo8 % 10000;
uint16_t lohi4 = lo8 / 10000;
uint16_t hilo4 = hi8 % 10000;
uint16_t hihi4 = hi8 / 10000;
// Could gate each of these memcpys with an if statement, then finding the zero cutoff is easier
memcpy(buffer, kBigStrings[hihi4], 4);
memcpy(buffer + 4, kBigStrings[hilo4], 4);
memcpy(buffer + 8, kBigStrings[lohi4], 4);
memcpy(buffer + 12, kBigStrings[lolo4], 4);
// Remove trailing zeros
char *mantissaEnd = buffer - 1; // Remove '.' if unnecessary
for (int i = 15; i >= 0; i--) {
if (buffer[i] != '0') {
mantissaEnd = buffer + i + 1;
break;
}
}
buffer = mantissaEnd; // Just overwrite what we had
// Add exponent
int32_t target = -pow10 + 17 - 1 /* account for digit to left of decimal */;
if (target == 0) {
*buffer = '\0';
return;
}
if (target < 0) {
(*buffer++) = '-';
target = -target;
}
(*buffer++) = 'E';
const char *string = kBigStrings[target];
if (unlikely(target >= 100)) {
(*buffer++) = string[1];
}
if (unlikely(target >= 10)) {
(*buffer++) = string[2];
}
(*buffer++) = string[3];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment