Skip to content

Instantly share code, notes, and snippets.

@aykevl
Created August 31, 2018 18:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aykevl/0fb6dbf6382e9f7557665e95607a59c3 to your computer and use it in GitHub Desktop.
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.
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)
}
@komuw
Copy link

komuw commented Nov 25, 2018

Coincidentally, Andrew Kelley was adding Parsing Floating Point Numbers to the zig[1] programming language
and he livestreamed the entire thing[2] (part 1, he's yet to do part 2)

  1. https://github.com/ziglang/zig
  2. https://www.youtube.com/watch?v=xyLxsAwUr90

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment