I'm guessing that you're seeing quantization - the output comes out looking "terraced", like rice fields. If that's the case it's because you're outputting 8 bit png, which only has 256 possible values.
you can do this with the command line tool gdal_translate. You should be able to run this from the terminal (linux/mac) or the osgeo4w shell (windows). I don't think you can do this from QGIS (correct me if I'm wrong)
assuming you've got a 1-band raster with float values in the range MIN to MAX, you can use
gdal_translate -of PNG -ot UInt16 -scale MIN MAX 0 65535 "source.tiff" "output.png"
replace MIN and MAX in that command with the minimum/maximum values in your raster. Make sure you set the MAX value to the full range, or the heightmap will be clipped, giving an appearance of mesas.
That will output a Unsigned Integer 16 bit png, with your float values mapped across the full 16-bit range of values.
You can find the real min/max using QGIS, or use the gdalinfo command line
gdalinfo -mm "source.tiff"
... and look at the metadata section for the minimum and maximum values. (-mm forces it to calculate the true range of values, rather than an estimate)