Skip to content

Instantly share code, notes, and snippets.

@codec-abc
Created June 4, 2021 18:45
Show Gist options
  • Select an option

  • Save codec-abc/bbfef8a64942a761081ed16aff797be8 to your computer and use it in GitHub Desktop.

Select an option

Save codec-abc/bbfef8a64942a761081ed16aff797be8 to your computer and use it in GitHub Desktop.
// Adapted from https://github.com/Oletus/float16-simulator.js
open System
open System.Numerics
let rec firstset (bits: uint) =
let value = if (bits &&& uint 0x80000000 <> uint 0) then 31 else firstset((bits <<< 1) ||| uint 1) - 1
1 <<< 8
let decomposeNumber (number: float) =
let bytes = BitConverter.GetBytes(number)
let sign = bytes.[7] >>> 7
let exponent = int ( ((bytes.[7] &&& byte 0x7f) <<< 4 ||| bytes.[6] >>> 4) - byte 0x3ff)
// Set the exponent to 0 (exponent bits to match bias)
bytes.[7] <- byte 0x3f
bytes.[6] <- bytes.[6] ||| byte 0xF0
let mantissa = BitConverter.ToDouble(bytes, 0)
(sign, exponent, mantissa)
let exponentBias (exponentBits: int) =
let possibleExponents = int <| Math.Pow(2.0, float exponentBits);
possibleExponents / 2 - 1
let maxNormalExponent (exponentBits: int) =
let possibleExponents = int <| Math.Pow(2.0, float exponentBits)
let bias = exponentBias(exponentBits)
let allExponentBitsOne = possibleExponents - 1
(allExponentBitsOne - 1) - bias
let toNumber (mantissa : int) (exponent: int) =
let sign = Math.Pow(-1.0, float (exponent &&& 0x80))
let endValue = (1.0 + float mantissa / Math.Pow(2.0, 8.0))
let exponent = Math.Pow(2.0, float (exponent &&& 0x7f) - 63.0)
sign * exponent * endValue
let froundBits (src, mantissaBits, exponentBits, clampToInf, flushSubnormal) =
// Note that Math.pow is specified to return an implementation-dependent approximation,
// but works well enough in practice to be used here for powers of two.
let possibleMantissas = Math.Pow(2.0, float mantissaBits)
let mantissaMax = 2.0 - 1.0 / possibleMantissas
let max = Math.Pow(2.0, float (maxNormalExponent(exponentBits))) * mantissaMax // value with all exponent bits 1 is special
let isInBadRange =
let tooBig =
if src > max then
if clampToInf then
Some(Double.PositiveInfinity)
else
Some(max)
else
None
let tooSmall =
if src < -max then
if clampToInf then
Some(Double.NegativeInfinity)
else
Some(-max)
else
None
if tooBig.IsSome then tooBig else if tooSmall.IsSome then tooSmall else None
if isInBadRange.IsSome then
isInBadRange.Value
else
let (sign, exponent_, mantissa) = decomposeNumber(src)
let mutable exponent = exponent_
// TODO: Customizable rounding (this is now round-to-zero)
let mutable mantissaRounded = Math.Floor(mantissa * possibleMantissas) / possibleMantissas;
let rounded =
if (exponent + exponentBias(exponentBits) <= 0) then
if flushSubnormal then
if sign <> byte 0 then
Some(-0.0)
else
Some(0.0)
else
let mutable result = None
while exponent + exponentBias(exponentBits) <= 0 do
exponent <- exponent + 1
mantissaRounded <- Math.Floor(mantissaRounded / 2.0 * possibleMantissas) / possibleMantissas
if mantissaRounded = 0.0 then
if sign <> byte 0 then
result <- Some(-0.0)
else
result <- Some(0.0)
result
else
None
if rounded.IsSome then
rounded.Value
else
let sign = if sign <> byte 0 then -1.0 else 1.0
sign * Math.Pow(2.0, float exponent) * mantissaRounded
let fromNumber (number :float, mantissaBits: int, exponentBits: int) =
let number = froundBits(number, mantissaBits, exponentBits, true, true)
let (sign, exponent, mantissa) = decomposeNumber number
let exponentValue = int <| exponent + exponentBias(exponentBits)
let mutable mantissaValue = uint <| mantissa * Math.Pow(2.0, float mantissaBits)
let higherBitFlag = firstset mantissaValue
let negated = uint (~~~higherBitFlag)
mantissaValue <- negated &&& mantissaValue
(sign, mantissaValue, exponentValue)
let (sign, mantissa, exponent) = fromNumber(57.4, 8, 7)
printfn "sign %i" sign
printfn "mantissa %d %s" mantissa (Convert.ToString(int mantissa, 2))
printfn "exponent %i %s" exponent (Convert.ToString(int exponent, 2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment