If you want to know the specifics, then the source code is the place to go.
As a general principle, a compute-bound process will use its entire time-slice more often than an I/O-bound process. Processes that have recently yielded without using their entire allocation are considered more likely to do the same in the near future, and can be scheduled accordingly.
It's important to remember that processes can and do change behaviour as they run; how long to decay the memory of past time-slices is one of the important tunables of most schedulers.
Real schedulers have to take into account more than just the historical time-slice usage: 'nice' value and locks that block other processes are just two examples of other information that may be used.