What happens when the attack speed is not at an integer multiple of a logical frame?

Frame: A logical update, 100ms delta.
Action: A thing to do (e.g. attack)
Length: The number of frames it takes to complete one action, as a float because we want to support fractions (this is the 3 and buffed 1.5 from your example).

On Start New Action Type:

FramesToAction = Length;

Logical Frame:

FramesToAction -= 1;
if (FramesToAction <= 0) {
    DoAction();
    FramesToAction += Length;
}

This logic will give the behaviour you desire, might need to be careful Length always is >= 1.