joeyh

rational 3d game?

joeyh at

3d games use floating point for speed, but could rational numbers be fast enough? After some benchmarking I think maybe so. Might be worth it in a game that involves many scales and/or vast distances.

I benchmarked ratios of Integers vs floats in haskell. Haskell's Ratio type is pretty optimised, and it supports unbounded Integers via gmp. Ratio Int is faster, but let's go all the way and have the positions of objects in the game support completely arbitrarily large/small numbers with no loss of precision.

Benchmark says: Adding and dividing are 25x slower. Comparing equality is only 3x slower, while comparing less-than is 5x slower. Take these numbers with some salt; very large/complicated values in ratios will use progressively more cpu time (and memory, for really big Integers!) Doesn't seem too horribly bad anyway.

Presumably at some point the Ratio Integer needs to be converted to a Float to be fed to graphics hardware to display the object. That takes 553 ns. But, you probably want to offset it first (ie, relative to camera position), so let's say 760 ns. Assuming you're doing that 60 times per second, with 1000 3d points in view, this conversion would use 12% of the total CPU.

So, if the points you want to represent with perfect precision are relatively small in number, say the centers of not too many objects, I think this could be perhaps a viable approach. Although this is not my area.. I mostly just want to see a game that scales from very large to very small to very distant without strange bugs from floating point errors.

benchmark code

import Data.Ratio
import Criterion
import Criterion.Main

main = defaultMain 
    [ bgroup "add1"
        [ bench "rational" $ whnf (r1 +) r1
        , bench "rational converted to frac" $
            whnf (\v -> realToFrac (r1 + v)) r1
        , bench "float" $ whnf (1.0 +) 1.0
        ]
    , bgroup "div3"
        [ bench "rational" $ whnf (r1 /) r3
        , bench "float" $ whnf (1.0 /) 3
        ]
    , bgroup "cmp"
        [ bench "rational" $
            whnf (\(a,b) -> r1 == a || r1 == b) (r3, r1)
        , bench "float" $
            whnf (\(a,b) -> 1.0 == a || 1.0 == b) (3, 1)
        ]
    , bgroup "lt"
        [ bench "rational" $
            whnf (\(a,b) -> r1 > a || r1 > b) (r3, r1)
        , bench "float" $
            whnf (\(a,b) -> 1.0 > a || 1.0 > b) (3, 1)
        ]
    ]

r1 :: Ratio Integer
r1 = 1%1 + 1000000 -- not simply 1%1 because the more complicated value makes the benchmark slower and thus more realistic

r3 :: Ratio Integer
r3 = 3%1

oops, I somehow pasted that as 1%1 + 1000000 where my actual benchmark was 1%1 + 1000000

joeyh at 2015-09-23T00:55:55Z

ooh, I found a bug in pumpa. :/

let me confirm it: 1%1 + 1000000

Above should read 1%1 + 1% followed by 1 and 7 zeros

joeyh at 2015-09-23T00:57:27Z

1%1 + 1% 10000000


(like that?)

JanKusanagi @identi.ca at 2015-09-23T02:19:27Z

Now I'm pondering using http://hackage.haskell.org/package/accelerate to implement hardware-accellerated rational arrays..

joeyh at 2015-09-23T02:48:30Z