Read, write floats in binary representation?

Is there a lib that makes it easy to read and write floats in a binary representation, e.g. where 0.011 means 0.375 ?

(This is not a question about reading and writing files. I’m more interested in embedding binary floats in source or even typing them at a utop prompt.)

1 Like

I’m assuming you don’t care about the actual base but about floating point binary layout specification. OCaml has built-in support for human readable specification of the binary layout of floats in hexadecimal notation.

For writing. Since 4.03 you can use the %h format specifier. Before that you you can use Gg.Float.pp whose doc string details the format.

For reading. float_of_string, which relies on the C strod function, will read back these representation on all OCaml versions.

A subset of the format can also be directly specified in OCaml sources, see the OCaml manual here for details.

Thanks @dbuenzli. I see that question was not clear enough. I don’t care at all about floating point binary layout within OCaml.

# Printf.printf "%h\n" 0.125;;
0x1p-3

But I would like “0.001” to be printed in response to an OCaml float 0.125, and I would like to be able to give “0.001” to a function and get back the OCaml float 0.125. It looks to me like strod and therefore float_of_string only know about decimal format, and hex format using “0x”.

(It looks like the value returned with “%h” conveys the information that I want from a representation of a float, but isn’t as easy to read. I did not see a way that fprintf allows replacing the “p-3” with “.00” in the printed output, but I may have missed some trick.)

I suspect you’ll have to write what you want yourself.

It shouldn’t be too hard. Just work on the binary representation of floats by using Int64.{float_of_bits,bits_of_float}.

This recapitulation of the representation may help as well as Stdlib.classify_float, e.g. that’s what Gg.Float.pp's implementation uses to work out the different cases.

1 Like

Thanks @dbuenzli. (A definitive “no” with tips for how to proceed is almost as good as a reference to an existing solution.)

It seems like, given what mars0i@ wants, the easiest thing would be to take the output of “%h” and hack on it?

  • split at the p (seems to always be present)
  • use the exponent to adjust the location of the decimal (zero-padding if needed)
  • convert hex-digits to four-digit binary numbers

That seems really straightforward and, it seems, reversible to produce a number in “%h” format.

1 Like

Thanks @Chet_Murthy. I probably won’t do it right away, but that’s helpful. Then again, you are describing a pretty simple hack. I might do it. No need to actually understand much. :slight_smile: But the pp source @dbuenzli pointed me to doesn’t look that difficult, either.

(I want an arbitrary number of digits, but I wouldn’t expect that that would be a difficulty.)

grin well, there are only a bounded number of nonzero digits, right? This is float-represented-by-binary, after all.

[Also, after a little more inspection, it seems the best would be to adjust the decimal -after- conversion to binary-representation.]

Yes–I’ll be satisfied with “finite but more than I will ever need”.

(Of course one may realize later that one does need more–as I have in the past–but not for this project.)

Uh, I think this is right. So bored.

let tobin f =
let s = Printf.sprintf "%h" f in
let s = String.sub s 2 ((String.length s) - 2) in
let [mant;exp] = String.split_on_char 'p' s in
let exp = int_of_string exp in
let mant,morexp = match String.split_on_char '.' mant with
[mant] -> mant,exp
  | [a;b] -> a^b, exp - 4 * (String.length b) in
let tobits = function
  | '0' -> "0000"
  | '1' -> "0001"
  | '2' -> "0010"
  | '3' -> "0011"
  | '4' -> "0100"
  | '5' -> "0101"
  | '6' -> "0110"
  | '7' -> "0111"
  | '8' -> "1000"
  | '9' -> "1001"
  | 'a' -> "1010"
  | 'b' -> "1011"
  | 'c' -> "1100"
  | 'd' -> "1101"
  | 'e' -> "1110"
  | 'f' -> "1111" in
let tobits_seq c = c |> tobits |> String.to_seq in
let mantbits = mant |> String.to_seq |> Seq.flat_map tobits_seq |> String.of_seq in
let fin =
if morexp >= 0 then mantbits^(String.make morexp '0')^"."
else
let pad_mantbits =
let mantlen = String.length mantbits in
if mantlen >= -morexp then mantbits else (String.make ((-morexp)-mantlen) '0')^mantbits in
let padmantlen = String.length pad_mantbits in
(String.sub pad_mantbits 0 (padmantlen - -morexp))^"."^(String.sub pad_mantbits (padmantlen - -morexp) (-morexp))
in
let finlen = String.length fin in
let fin = match String.index_opt fin '1', String.index_opt fin '.' with
  | Some onepos, Some dotpos when onepos < dotpos -> String.sub fin onepos (finlen - onepos)
  | None, Some dotpos when dotpos > 1 -> String.sub fin (dotpos - 1) (finlen - (dotpos - 1))
  | _ -> fin
in fin
;;

val tobin : float -> string = <fun>
# tobin 0. ;;
- : string = "0."
# tobin 0.125 ;;
- : string = "0.001"
# tobin 0.03125 ;;
- : string = ".00001"
# tobin 256. ;;
- : string = "100000000."
# 

Think I fixed stripping superfluous leading zeroes.

2 Likes

How nice of you, Chet. I’ll try that. Thanks.