11

How do I modify AutoFixture create method for float, double and decimal, so that when these types get created they will also have a remainder?

Currently I do this, but this throws exception.

var fixture = new Fixture();
fixture.Customize<double>(sb => sb.FromFactory<double>(d => d * 1.33));   //This should add remainder
var value = fixture.Create<double>();
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Rok
  • 705
  • 8
  • 20

3 Answers3

13

Attempting to redefine a type (double) by using a value of the same type (double) will, indeed, yield an infinite recursion. However, you can easily make this work by changing the seed input into another type - e.g. an int:

var fixture = new Fixture();
fixture.Customize<double>(c => c.FromFactory<int>(i => i * 1.33));
var value = fixture.Create<double>();

Doubles will now tend to have fractional values too.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • I guess I haven't really thought about how things work in the background and that customization was creating an infinite recursion. Thanks for clarifying this! – Rok Jul 17 '13 at 06:54
  • I didn't realize AutoFixture generates only integral floating point numbers! that seems completely odd/wrong. I just wrote my own `CreateDecimal`, etc. extension methods that add `Random.NextDouble()` to the result, but I've been using `Create` for some time assuming the results would have fractional parts. – Dave Cousineau Aug 28 '23 at 02:04
5

One option is to use a custom ISpecimenBuilder:

var fixture = new Fixture();
fixture.Customizations.Add(
    new RandomDoublePrecisionFloatingPointSequenceGenerator());

The RandomDoublePrecisionFloatingPointSequenceGenerator could look like below:

internal class RandomDoublePrecisionFloatingPointSequenceGenerator
    : ISpecimenBuilder
{
    private readonly object syncRoot;
    private readonly Random random;

    internal RandomDoublePrecisionFloatingPointSequenceGenerator()
    {
        this.syncRoot = new object();
        this.random = new Random();
    }

    public object Create(object request, ISpecimenContext context)
    {
        var type = request as Type;
        if (type == null)
            return new NoSpecimen(request);

        return this.CreateRandom(type);
    }

    private double GetNextRandom()
    {
        lock (this.syncRoot)
        {
            return this.random.NextDouble();
        }
    }

    private object CreateRandom(Type request)
    {
        switch (Type.GetTypeCode(request))
        {
            case TypeCode.Decimal:
                return (decimal)
                    this.GetNextRandom();

            case TypeCode.Double:
                return (double)
                    this.GetNextRandom();

            case TypeCode.Single:
                return (float)
                    this.GetNextRandom();

            default:
                return new NoSpecimen(request);
        }
    }
}
Nikos Baxevanis
  • 10,868
  • 2
  • 46
  • 80
  • Nikos thanks for the help, but Mark's answer is more inline with what I was searching. – Rok Jul 17 '13 at 06:56
  • 1
    +1 Any reason for making this SpecimenBuilder thread safe - do you do this for all your builders? (I understand why a Random needs to be guarded bit it never occurred to me to add this guarding in a SpecimenBuilder. I'd never though of Fixture and/or Generators emanating from same as being guaranteed threadsafe. I certainly can understand how, esp given @ploeh's Immutability work in V3.0 that it might be relatively easy enough to achieve). Or have I missed a blog post :P – Ruben Bartelink Jul 17 '13 at 21:21
  • +1 This one could also work without the thread safety. Almost all numeric sequence generators are thread safe though.. Most of them (if not all) were created prior to 3.0. – Nikos Baxevanis Jul 18 '13 at 07:49
0

There doesn't seem to be a way to intercept/modify created values, but a solution I think I prefer is to use a second Fixture.

public static IFixture FixDecimals(
   this IFixture me
) {
   var f = new Fixture();
   var r = new Random();
   me.Register<float>(() => f.Create<float>() + r.NextSingle());
   me.Register<double>(() => f.Create<double>() + r.NextDouble());
   me.Register<decimal>(() => f.Create<decimal>() + (decimal)r.NextDouble());
   return me;
}

I think 'normally' you could use something like the following, but it doesn't work in these cases because these are value types.

// doesn't work
var r = new Random();
me.Customize<float>(f => f.Do(_f => _f += r.NextSingle()));

This doesn't work because Do isn't a function but wants you to modify the 'instance', but since these are values types, modifying the 'instance' does nothing because we have a value not an object instance.

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80