Skip to content

Word type to Double or Float conversions are slower than Int conversions

We have int2Double# and int2Float# primitives, but not equivalent ones for Word types. We may need word2Double# too, for Words* to be fully first-class performance-wise.

This means we have to do extra tests in the Num instances for Word types to implement 'fromIntegral':

    toInteger (W# x#)
        | i# >=# 0#             = smallInteger i#
        | otherwise             = wordToInteger x#
        where i# = word2Int# x#

Now, for some types, we work around this:

"fromIntegral/Int->Word"  fromIntegral = \(I# x#) -> W# (int2Word# x#)
"fromIntegral/Word->Int"  fromIntegral = \(W# x#) -> I# (word2Int# x#)
"fromIntegral/Word->Word" fromIntegral = id :: Word -> Word

and so on for other Word/Int types. And all is fine.

The problem comes up for Float and Double. For Int, we can write:

"fromIntegral/Int->Float"   fromIntegral = int2Float
"fromIntegral/Int->Double"  fromIntegral = int2Double

int2Float :: Int -> Float
int2Float   (I# x) = F# (int2Float# x)

int2Double :: Int -> Double 
int2Double   (I# x) = D# (int2Double#   x)

But we can't write these rules for Word types.

The result is a slow down on Word conversions, consider this program:

main = print . sumU
             . mapU (fromIntegral::Int->Double)
             $ enumFromToU 0 100000000

When in lhs is Int, we get this nice code:

$wfold :: Double# -> Int# -> Double#

$wfold =
  \ (ww_s18k :: Double#) (ww1_s18o :: Int#) ->
    case ># ww1_s18o 100000000 of wild_a14T {
      False ->
        $wfold
          (+## ww_s18k (int2Double# ww1_s18o)) (+# ww1_s18o 1);
      True -> ww_s18k

But for Word types, we get:

$wfold :: Double# -> Word# -> Double#

$wfold =
  \ (ww_s1gN :: Double#) (ww1_s1gR :: Word#) ->
    case gtWord# ww1_s1gR __word 100000000 of wild_a1do {
      False ->
        case case >=# (word2Int# ww1_s1gR) 0 of wild1_a1cS {
               False ->
                 case word2Integer# ww1_s1gR of wild11_a1d9 { (# s_a1db, d_a1dc #) ->
                 case {__ccall __encodeDouble Int#
                        -> ByteArray#
                        -> Int#
                        -> State# RealWorld
                        -> (# State# RealWorld, Double# #)}_a1bT
                        s_a1db d_a1dc 0 realWorld#
                 of wild12_a1bX { (# ds1_a1bZ, ds2_a1c0 #) ->
                 ds2_a1c0
                 }
                 };
               True -> int2Double# (word2Int# ww1_s1gR)
             }
        of wild1_a1bM { __DEFAULT ->
        $wfold
          (+## ww_s1gN wild1_a1bM) (plusWord# ww1_s1gR __word 1)
        };
      True -> ww_s1gN
    }

Which is to be expected, and the running time goes from:

$ time ./henning  
5.00000000067109e17
./henning  1.53s user 0.00s system 99% cpu 1.534 total

To:

$ time ./henning  
5.00000000067109e17
./henning  4.57s user 0.00s system 99% cpu 4.571 total

So not too bad, but still, principle of least surprise says Word and Int should behave the same.

Should we have a word2Double# primop?

Trac metadata
Trac field Value
Version 6.8.2
Type FeatureRequest
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system Unknown
Architecture Unknown
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information