For anyone interested, here's my little python script for simulating bit truncation:
import numpy as np
# test truncation/rounding on a sequence of numbers with random values
divs = [2, 4, 8, 16] # test with these divisors
methods = ['ceil', 'round'] # test with these truncation methods
Xlength = 128 # length of each sequence
Niter = 32 # number of iterations to perform, each on a different X
Xrms = 64 # RMS of noise X
# pre allocate saved data
Xall = np.zeros((Niter, Xlength))
Yall = np.zeros_like(Xall)
Xmean = np.zeros((Niter, 1))
Ymean = np.zeros_like(Xmean)
# generate Niter random X sequences
for idx in range(Niter):
X = np.random.normal(loc=0.0, scale=Xrms, size=Xlength)
X = np.round(X)
Xall[idx, :] = X + 4 * Xrms # offset so X is always positive
for div in divs:
for method in methods:
for idx in range(Niter):
# get pseudorandom X
X = np.squeeze(Xall[idx, :]).copy()
# choose truncation method and calculate output Y
if method == 'floor':
Y = np.floor(X/div)*div
elif method == 'ceil':
Y = np.ceil(X/div)*div
elif method == 'round':
# add a tiny bit to X before rounding, to avoid wrong result due to float precision
Y = np.round(X/div+Xrms*1e-6)*div
# save Y to Yall
Yall[idx, :] = Y
# save mean of X and Y
Xmean[idx] = np.mean(X)
Ymean[idx] = np.mean(Y)
# get mean error Emean (the "DC bias")
Emean = np.mean(Ymean-Xmean)
# AC RMS of Y (ignores DC component)
Yacrms = (np.mean((Yall-np.mean(Ymean))**2))**0.5
# get the AC RMS error between Y and X (ignores DC bias)
Eacrms = (np.mean((Yall-Emean-Xall)**2))**0.5
# print results
print('div={0}, method={1}, Emean={2:.3f}, Yacrms={3:.3f}, Eacrms={4:.3f}'.format(div, method, Emean, Yacrms, Eacrms))
print('done')
Here's the output:
div=2, method=ceil, Emean=0.509, Yacrms=64.009, Eacrms=0.500
div=2, method=round, Emean=0.509, Yacrms=64.009, Eacrms=0.500
div=4, method=ceil, Emean=1.505, Yacrms=64.031, Eacrms=1.117
div=4, method=round, Emean=0.493, Yacrms=64.016, Eacrms=1.116
div=8, method=ceil, Emean=3.455, Yacrms=64.132, Eacrms=2.271
div=8, method=round, Emean=0.562, Yacrms=63.970, Eacrms=2.283
div=16, method=ceil, Emean=7.477, Yacrms=64.255, Eacrms=4.559
div=16, method=round, Emean=0.383, Yacrms=64.258, Eacrms=4.659
done
So the benefit of rounding vs ceil/floor truncation is clear when looking at Emean (the DC bias). The truncation method doesn't seem to impact the output noise level at all (Yacrms).
Note that these results are with the input noise level well above the quantization threshold (64LSB in this case), as would be expected at the output of an un-optimized decimation filter.