You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/docs/javascript/compatibility.md
+66Lines changed: 66 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -167,3 +167,69 @@ let inline doesWork(x: 'T) =
167
167
168
168
doesWork 5
169
169
```
170
+
171
+
## Numeric types
172
+
173
+
174
+
In Fable, we use F# numeric types, which are all translated to JS Number (64-bit floating type) at the exception of `int64`, `uint64`, `bigint` and `decimal`.
175
+
176
+
Fable numbers are very nearly compatible with .NET semantics, but translating into Javascript types has consequences:
177
+
178
+
* (non-standard) All floating point numbers are implemented as 64 bit (`double`). This makes `float32` numbers more accurate than expected.
179
+
* (non-standard) Arithmetic integers of 32 bits or less are implemented with different truncation from that expected, as whole numbers embedded within `double`.
180
+
* (OK) Conversions between types are correctly truncated.
181
+
* (OK) Bitwise operations for 64 bit and 32 bit integers are correct and truncated to the appropriate number of bits.
182
+
* (non-standard) Bitwise operations for 16 bit and 8 bit integers use the underlying JavaScript 32 bit bitwise semantics. Results are not truncated as expected, and shift operands are not masked to fit the data type.
183
+
* (OK) Longs have a custom implementation which is identical in semantics to .NET and truncates in 64 bits, although it is slower.
184
+
185
+
32 bit integers thus differ from .NET in two ways:
186
+
187
+
* Underlying 52 bit precision, without expected truncation to 32 bits on overflow. Truncation can be forced if needed by `>>> 0`.
188
+
* On exceeding 52 bits absolute value floating point loses precision. So overflow will result in unexpected lower order 0 bits.
189
+
190
+
The loss of precision can be seen in a single multiplication:
191
+
192
+
```fsharp
193
+
((1 <<< 28) + 1) * ((1 <<< 28) + 1) >>> 0
194
+
```
195
+
196
+
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 `0x2000_0001`.
197
+
198
+
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:
199
+
200
+
```fsharp
201
+
let rng (s:int32) = 10001*s + 12345
202
+
```
203
+
204
+
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:
205
+
206
+
```fsharp
207
+
let rec randLst n s =
208
+
match n with
209
+
| 0 -> [s]
210
+
| n -> s :: randLst (n-1) (rng s)
211
+
212
+
List.iter (printfn "%x") (randLst 7 1)
213
+
```
214
+
215
+
The resulting printed list of pseudo-random numbers does not work in Fable:
216
+
217
+
| Fable | .NET |
218
+
|-------:|------:|
219
+
|1|1|
220
+
|574a|574a
221
+
|d524223|d524223|
222
+
|6a89e98c|6a89e98c|
223
+
|15bd0684|15bd0685|
224
+
|3d8b8000|3d8be20e|
225
+
|50000000|65ba5527|
226
+
|0|2458c8d0|
227
+
228
+
### Workarounds
229
+
230
+
* When accurate low-order bit arithmetic is needed and overflow can result in numbers larger than 2^53 use `int64`, `uint64`, which use exact 64 bits, instead of `int32`, `uint32`.
231
+
* Alternately, truncate all arithmetic with `>>> 0` or `>>> 0u` as appropriate before numbers can get larger than 2^53: `let rng (s:int32) = 10001*s + 12345 >>> 0`
232
+
233
+
### Printing
234
+
235
+
One small change from .NET in `printf`, `sprintf`, `ToString`. Negative signed integers are printed in hexadecimal format as sign + magnitude, in .NET they are printed as two's complement bit patterns.
0 commit comments