Strange thing about displayObject.alpha property. (Is it a bug?)

I found the arithmetic calculation on diaplayObject.alpha field is very strange. Look the following very simple code


local img = display.newImage(‘moon.png’)

  

  local da = -1/600

  local a = 1

  for k = 1, 600 do

    img.alpha = img.alpha + da

    a = a + da

    print(string.format(’%d,a:%.6f, alpha:%.6f’, k, a, img.alpha))

end


As a matter of course, I thought that the ‘a’ and ‘img.alpha’ must be the same at each steps.

But, the result is as follows:

14:29:40.092  k=249, a:0.585000, alpha:0.023529

14:29:40.092  k=250, a:0.583333, alpha:0.019608

14:29:40.092  k=251, a:0.581667, alpha:0.015686

14:29:40.092  k=252, a:0.580000, alpha:0.011765

14:29:40.092  k=253, a:0.578333, alpha:0.007843

14:29:40.092  k=254, a:0.576667, alpha:0.003922

14:29:40.092  k=255, a:0.575000, alpha:0.000000

14:29:40.092  k=256, a:0.573333, alpha:0.000000

14:29:40.092  k=257, a:0.571667, alpha:0.000000

14:29:40.092  k=258, a:0.570000, alpha:0.000000

14:29:40.092  k=259, a:0.568333, alpha:0.000000

The variable ‘a’ becomes zero at k=600. However, img.alpha becomes 0 at k=255.

There is a huge gap is there.

The alpha property of display object is not an ordinary Lua number ?

Am I missing something ?

The locals a and da may be stored with a different precision than the field alpha.

i.e. The locals may be doubles and the alpha field may be a single.

Alpha is very probably an 8bit integer value internally and changes to it’s value, if not zero, rounded down/up to at least -1 or +1. Each time you read the property in your loop (img.alpha = img.alpha + da) you get the current 8bit integer casted into a float, modified by at least +/- 1 and then written back to the object as the 8bit integer.

If you don’t increment the alpha property of the display object but track a value yourself and just assign the current value whenever you change it, it’s working the way you want it to - but of course still with small differences as the actual alpha value can only be a multiple of 1/255

[lua]

local img = display.newRect(1, 1, 100, 100)

img.myAlpha = 1

local da = -1/600

local a = 1

for k = 1, 600 do

    img.myAlpha = img.myAlpha + da

    img.alpha = img.myAlpha

    a = a + da

    print(string.format(’%d,a:%.6f, alpha:%.6f’, k, a, img.alpha))

end

[/lua]

If this is true, I think it is a serious problem.

For example, assume the task that a display object is to be faded out and must be invisible exactly 10 seconds after.

If obj.alpha==1 now and adding -1/600 to obj.alpha in every frame (in the case of fps=60) is expected to do the task.

But, it does not works that way if obj.alpha is float type(of C/C++/Java whatever).

I found that ‘x’, ‘y’, ‘rotation’, ‘xScale’, ‘yScale’ properites have not this kind of problem.

Only ‘alpha’ property has.

It can be evaded the timing problem as you suggested.

However, it is easy for beginners to misunderstand that ‘alpha’ is an ordinary Lua number.

I personally wish alpha itself is of type double,and it is converted to a byte only when it is processed internally.

(as Gideros does so)

A moot point as the entire loop will execute before the next frame is rendered anyway.

And, I defy anyone to notice a 1/255 change in alpha!

I expect that code to be meant as a sample focused on the problem and a realworld sample would of course change the values in f.i. an enterFrame handler. Like, say an entity moves from left to right on the screen and while doing so fade alpha to 0. If you do this in a cummulative way, like the original code, depending on the speed of the entity and the framerate, the value might have fully changed after only half of the distance (or any other arbitrary numbe distance as it depends on the speed of change and movement).

This won’t happen if you interpolate between a start and end value at any point between the start and end value.

If you wanted an object to be invisible in exactly 10s then you certainly wouldn’t use enterFrame and it is hardly a “serious problem” to have a tiny fractional difference.

We all know lua numbers are not exactly accurate when considering floating point calculations.  This is why we round/floor most calculations and ignore the fractional differences.

I get the “issue” when your used to 1 - 0.5 = 0.5 and not 0.4999999900005463628!

lua is odd sometimes…

Sure, that’s the same for all floating point values on computers but this has nothing todo with what happens here.

It is a rounding “error” in a much more significant difference than any floating point number error and it is caused by just an implementation detail of how Corona stores alpha values internally (i.e. 8bit integers) and how it rounds changes to alpha coming from Lua (floats).

I.e. issue is not 1-0.5 = 0.49999999999999999999

It is

1 - 0.001 = 0.996

1 - 0.002 = 0.996

1 - 0.003 = 0.996

1 - 0.004 = 0.992

1 - 0.005 = 0.992

1 - 0.006 = 0.992

1 - 0.007 = 0.992

And the reason is simply that a range mapped from 0 to 1 to a byte means the smallest possible difference is 1/255, i.e. 0.004

(All numbers truncated as it makes the effect more clear)

It’s also not relevant if there are better approaches to fade an object in 10 seconds. When you stumble over such an “effect” it’s important to know what happens and why otherwise you may have a hard time finding your bugs when the logic of the code is, in theory, 100% correct.

This is a situation that can happen in countless situations it’s just rare in Lua as there’s no integer number type (not in Lua 5.1).

My point was (using your figures) no one can tell the difference of an alpha value of 0.996 or 0.992.  The difference is imperceptible.

I animate alpha all the time and I don’t think I ever have a time frame over 500ms.

I do love a technical debate but, for alpha, this really is a non-issue.

I do agree to M. Flad. It is a serious problem that alpha is handled and even calculated as int8.

This causes logical error (that is very hard to catch) when alpha is updated in enterframe handler.

I made a comparing video with Corona and Gideros simulators.

The same code is used in enterframe hander.

moon.alpha = moon.alpha - 1/600

I took long time to understand what is wrong with corona.

https://youtu.be/rTYvP6rk6oY

You can see the left (corona) moon fades out faster tham the one right (gideros) one.

I don’t think it’s a serious issue as in, it’s wrong how Corona does it, it’s just a decision that was once made and it very rarely is resulting in anything going wrong - as SGS I’d not handle an alpha fade like this.

In this very situation it’s just the mix that it’s a reasonably uncommon usage (cummulate modify existing alpha value instead of just assigning a new value based on time etc.) and in Corona/Lua in general it’s a very rare case as every number is typically a double internally.

Just changing alpha to doubles here would solve this situation but f.i. if (*searching for the old roadmaps*) we get f.i. per vertex colors you’d have the same situation because you clearly don’t want to waste 32bytes for the rgba values of each vertex. And using 2 different ways to handle color values would be the worst decision.

Also just because one can’t think about a use case does not mean there does not exist one. I can very much see enter frame alpha changes and it’s pretty easy to get into a range where it will result in errors.

Assume f.i. you have enemies appearing in a level and you want them to appear over a duration of 5 seconds, at 60fps that’s an alpha change of 1/300. You start with alpha 0 and add 1/300 per update to your alpha value … and your welcome to an enemy with an alpha value that’s still 0 and completely invisible even after hours because in this case, when adding instead of subtracting, the rounding results in no change at all.

Again I think it is important to know what happens because those kinds of situations are everywhere in programming in general. This is how you get experience with hard to find bugs. Maybe 5 years from now, in a different language, another framework and a very different value you calculate you’ll have an issue with a similar root cause, you may just find the bug much faster now :slight_smile: