In Fable, we use F# numeric types, which are all translated to JS Number (64-bit floating type) at the exception of
- (non-standard) All floating point numbers are implemented as 64 bit (
double). This makes
float32numbers more accurate than expected.
- (non-standard) Arithmetic integers of 32 bits or less are implemented with different truncation from that expected, as whole numbers embedded within
- (OK) Conversions between types are correctly truncated.
- (OK) Bitwise operations for 64 bit and 32 bit integers are correct and truncated to the appropriate number of bits.
- (OK) Longs have a custom implementation which is identical in semantics to .NET and truncates in 64 bits, although it is slower.
32 bit integers thus differ from .NET in two ways:
- Underlying 52 bit precision, without expected truncation to 32 bits on overflow. Truncation can be forced if needed by
- On exceeding 52 bits absolute value floating point loses precision. So overflow will result in unexpected lower order 0 bits.
The loss of precision can be seen in a single multiplication:
((1 <<< 28) + 1) * ((1 <<< 28) + 1) >>> 0
The multiply product will have internal double representation rounded to
0x0100_0000_2000_0000. When it is truncated to 32 bits by
>>> 0 the result will be
0x2000_0000 not the .NET exact lower order bits value of
The same problem can be seen where repeated arithmetic operations make the internal (non-truncated) value large. For example a linear congruence random number generator:
let rng (s:int32) = 10001*s + 12345
The numbers generated by repeated application of this to its result will all be even after the 4th pseudo-random number, when
s value exceeds 2^53:
let rec randLst n s = match n with | 0 -> [s] | n -> s :: randLst (n-1) (rng s) List.iter (printfn "%x") (randLst 7 1)
The resulting printed list of pseudo-random numbers does not work in Fable:
- When accurate low-order bit arithmetic is needed and overflow can result in numbers larger than 2^53 use
uint64, which use exact 64 bits, instead of
- Alternately, truncate all arithmetic with
>>> 0uas appropriate before numbers can get larger than 2^53:
let rng (s:int32) = 10001*s + 12345 >>> 0
One small change from .NET in
ToString. Negative signed integers are printed in hexadecimal format as sign + magnitude, in .NET they are printed as two's complement bit patterns.