Created
August 31, 2018 18:24
-
-
Save aykevl/0fb6dbf6382e9f7557665e95607a59c3 to your computer and use it in GitHub Desktop.
Attempt to print floats. Turns out this is *really* difficult. This is my attempt so far: it isn't entirely correct.
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
import "unsafe" | |
func printfloat32(f float32) { | |
// Here be dragons. | |
// Correctly printing floats turns out to be really, really difficult (there | |
// have been multiple research papers on the topic). Instead of aiming to be | |
// exact, I've tried to print something that isn't wrong in at least most | |
// cases (hopefully) and doesn't look too bad. Also, I want this to be | |
// really small. | |
// TODO: make this smaller and/or more precise. Also, print some values like | |
// 12.5 without exponent to look better. This is mostly to have something | |
// working. | |
// Some resources: | |
// https://blog.benoitblanchon.fr/lightweight-float-to-string/ | |
// http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ | |
// https://en.wikipedia.org/wiki/Single-precision_floating-point_format | |
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf | |
// Convert the float to an integer with some pointer hacking. | |
i := *(*uint32)(unsafe.Pointer(&f)) | |
// Break apart this int into its components. | |
sign := i >> 31 | |
exponent := int32((i >> 23) & 0xff) // 8 bits | |
mantissa := i & 0x7fffff // 23 bits | |
// Handle special values: Infinity and NaN. | |
if sign != 0 { | |
putchar('-') | |
} | |
if exponent == 0xff { | |
if mantissa == 0 { | |
print("inf") | |
} else { | |
// TODO: what about -nan? I don't think that should be printed. | |
print("nan") | |
} | |
return | |
} | |
if exponent != 0 { | |
// This is a denormalized number, normalize it by adding the implicit | |
// leading bit at position 24. | |
mantissa |= 1 << 23 | |
} | |
// The idea of the algorithm is the following: mold the mantissa in such a | |
// way that in the end it represents a 7-digit number when dropping the | |
// digits after the floating point (as calculating the digits before the | |
// floating point is trivial). This is done with the following operations, | |
// where necessary: | |
// - Multiply/divide by 10 while adjusting pow10. | |
// This moves the decimal point of the number. | |
// - Move the binary point (rshift) by 4 bits, to use all bits possible. | |
// This shifts the mantissa by 16 bits and adjusts the rshift as well. | |
// The number itself is unchanged, just the used bits in the floating | |
// point representation. | |
// Here, rshift is the amount to shift the mantissa to get the number before | |
// the floating point (mantissa >> rshift), and pow10 is how much the | |
// decimal floating point has moved by the above operations. | |
rshift := 150 - int32(exponent) | |
pow10 := int32(6) | |
// Adjust very big numbers. | |
for rshift < 8 { | |
// make sure we use as much bits as possible | |
if (mantissa >> 28) != 0 { | |
mantissa /= 10 | |
pow10++ | |
} | |
// move the binary exponent point | |
rshift += 4 | |
mantissa *= 16 | |
// multiply answer with 10 to get the range back | |
mantissa /= 10 | |
pow10++ | |
} | |
// Adjust very small numbers. | |
for rshift >= 32 { | |
// make sure we use as much bits as possible | |
if (mantissa >> 28) == 0 { | |
mantissa *= 10 | |
pow10-- | |
} | |
// move the binary exponent point | |
rshift -= 4 | |
mantissa /= 16 | |
// multiply answer with 10 to get the range back | |
mantissa *= 10 | |
pow10-- | |
} | |
// Get already-sensible numbers in exactly 7 digits. | |
for (mantissa >> uint32(rshift)) < 1000000 { | |
// make sure we use as much bits as possible | |
if (mantissa >> 28) == 0 { | |
mantissa *= 10 | |
pow10-- | |
} | |
// move the binary exponent point | |
rshift -= 4 | |
mantissa /= 16 | |
// multiply answer with 10 to get the range back | |
mantissa *= 10 | |
pow10-- | |
} | |
number := mantissa >> uint32(rshift) | |
if ((mantissa >> uint32(rshift - 1)) & 1) == 1 { | |
// round up | |
number += 1 | |
if number == 10000000 { | |
number /= 10 | |
} | |
} | |
digits := [7]byte{} | |
// Fill in all 10 digits. | |
for i := 6; i >= 0; i-- { | |
digit := byte(number % 10 + '0') | |
digits[i] = digit | |
number /= 10 | |
} | |
// Print the float. | |
putchar(digits[0]) | |
putchar('.') | |
for i := 1; i < len(digits); i++ { | |
putchar(digits[i]) | |
} | |
putchar('e') | |
printint32(pow10) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Coincidentally, Andrew Kelley was adding
Parsing Floating Point Numbers
to the zig[1] programming languageand he livestreamed the entire thing[2] (part 1, he's yet to do part 2)