View Full Version : Question regarding assert


hello_hello
5th April 2019, 18:07
This isn't exactly a real world example but it illustrates my question. I'm using classic Avisynth but I'll assume Avisynth+ works the same way.

For the example below, A is true, so Avisynth is satisfied and continues on without displaying the error message.

A = true
assert(A || (B == 10), "blah, blah")

For this example though, I'm using a function for converting float to fractions as part of the error message, and as a result Avisynth stops with an "I don't know what B means" error.

A = true
assert(A || (B == 10), "blah, blah" + FloatToFraction(B))

I discovered you can prevent Avisynth from parsing the error message unnecessarily by doing something like this:

A = true
assert(A || (B == 10), A || (B == 10) ? "" : "blah, blah" + FloatToFraction(B))

but my question is, why does Avisynth stop parsing the conditions as soon as it's satisfied, but still parses an error message it doesn't have to display?

Cheers.

function FloatToFraction(float Decimal) {
Numerator = string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")" }

Gavino
5th April 2019, 18:28
why does Avisynth stop parsing the conditions as soon as it's satisfied, but still parses an error message it doesn't have to display?
Because the error message expression, as an argument of assert(), is parsed in the outer script before assert() is called.
At that point, it doesn't know whether it will be used or not.

hello_hello
5th April 2019, 18:59
Thanks for the response.

From a purely "user" perspective, it seemed a bit odd to me, but at least I know what to expect now, and/or how to work around it.

Cheers.

StainlessS
5th April 2019, 20:18
Function FloatToFraction(float Decimal) {
Numerator = string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")"
}

A=True # Both Below will Error if A=False and B not defined

# Either
(A) ? NOP : assert(B == 10, "blah, blah" + FloatToFraction(B))

# OR
(A || B == 10) ? NOP : Assert(false,"blah, blah" + FloatToFraction(B))


Return MessageClip("Done")

hello_hello
6th April 2019, 04:25
I guess normally B would be defined, but an undefined B made the example simpler.

This still results in an error, just a different one. It's closer to the problem I was actually having. At first I assumed both A & B would have to be greater than zero for the error message to get a look-in.

A = 0
B = 0
assert((A == 0) || (B == 0) || (A >= B), "blah, blah" + FloatToFraction(B))

I think I stumbled around it by doing something like this at the time, but with both A & B defined, there's no doubt several/better/other ways.

A = 0
B = 0
(B > 0) ? Eval(""" assert((A == 0) || (A >= B), "blah, blah" + FloatToFraction(B)) """) : nop()

StainlessS
6th April 2019, 05:39
I guess normally B would be defined, but an undefined B made the example simpler.
Not really, makes it a bit tricky to figure out what you are trying to do.

0.0 as a Fraction, dont think so.

Function FloatToFraction(float Decimal) {
Numerator = string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")"
}

x=FloatToFraction(0.0)
Return BlankClip.Subtitle(String(x))

ContinuedFraction: Float Value Out Of Range for Rational Pair.
You're gonna have some kind of Divide by zero in there somewhere.

I still dont got no clue what you are trying to do.

You can only do your Assert Message when B is non zero, else you get an entirely different error to the one you are trying to flag.

Maybe get you nearer to your mystical goal.

A = 0
B = 0
Assert(0<B, "blah, blah" + " Divide by Zero Imminent")
Assert(A==0 || B<=A, "blah, blah" + FloatToFraction(B)) # I dont like this, A=0, OR, 0 < B <= A [A ok if either below B(ie 0) or greater or equal to B, Not necessarily wrong but are you sure]
Return MessageClip("Done")


EDIT: Below shows 1:2, Is that correct (what you want), I've no idea.

x=FloatToFraction(-0.5)
Return BlankClip.Subtitle(String(x))

hello_hello
6th April 2019, 17:22
Yeah, the divide by zero error message is the one I was getting. Assuming B was undefined when I asked the question originally was just to keep the example simple.

What I intended, was for assert to be true if either A or B were zero, but if both were greater than zero then A had to be greater than or equal to B, otherwise the error message would display and include the current value for B (as a fraction). A and B would be float.

Unless I'm losing the plot, this achieves what I intended, minus the function in the error message. I just didn't expect the error message to be parsed unless the assert conditions were false.

A = 0.0
B = 0.0
assert((A == 0.0) || (B == 0.0) || (A >= B), "blah, blah")

It's sorted now anyway. Using Eval did the trick.

Cheers.

StainlessS
6th April 2019, 22:23
Thanks HH, I got it just fine from your verbal description.

Maybe too late for you now, but here tis anyways. (although I made it NOP if A and/or B is Smaller or equal to 0.0 rather than just equal to : maybe you change it back).

Function FloatToFraction(float Decimal) {
Numerator = string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")"
}

A = 0.0
B = 0.0

# NOP Replaceable with below alternate blah,balh [where Assert(False, ...), means we already know that we have error]
(A<=0.0 || B<=0.0)
\ ? NOP [* Assert(False,"blah, blah" + "Some Other Error Message") *]
\ : Assert(A>=B, "blah, blah" + FloatToFraction(B))

Return MessageClip("Done")


EDIT: If thats for Aspect Ratio, I seen (probably have) clips that are portrait AR. [EDIT: But of course, Mobile phone stuff gots plenty]

EDIT:
Bit more play

Function FloatToFraction(float Decimal) {
Numerator = string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")"
}

A = 0.0
B = 0.0

(A<=0.0 || B<=0.0) [* <<<<<<<<<<<<<<<<<<<<<<<<<<<< Enclosed in Parenthesis >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*]
\ ? Assert(False,"blah, blah " + ( A<=0.0&&B<=0.0 ? "A and B Borked" : B<=0.0 ? "B Borked" : "A Borked, B="+FloatToFraction(B)) )
\ : Assert(A>=B, "blah, blah" + FloatToFraction(B))

Return MessageClip("Done")

Because the error message expression, as an argument of assert(), is parsed in the outer script before assert() is called.
At that point, it doesn't know whether it will be used or not.

The below part is only evaluated after establishing that B > 0.0 via the ternary ? and : stuff [enclosed in the Parenthesis] and is the argument of assert() as said by Gavino.
"A Borked, B="+FloatToFraction(B))

hello_hello
7th April 2019, 01:07
It is for aspect ratio. Checking the specified output and cropping display aspect ratios for the script.

If either has a value of zero, they're considered undefined by the script, so zero is the default for each and I don't need to check for that here. Only what's happening if they're greater than zero.

When the script's operating in the mode where it can only resize the width, a specified output display aspect ratio can't be less than a specified cropping display aspect ratio. If OutDAR >= CropDAR, the script adds borders to the sides as required and resizes according to the specified width.

Without using Eval, I think this is probably the easiest way, including checking for the mode the script is operating in. Similar to your suggestion, without having to produce an error message for zero values.

ResizeWO = true
OutDAR = 0.0
CropDAR = 0.0
assert(!ResizeWO || OutDAR==0.0 || CropDAR==0.0 || OutDAR>=CropDAR, \
CropDAR==0.0 ? "" : "blah, blah" + FloatToFraction(CropDAR))

Although thinking about it, if I modified the FloatToFraction function instead......

function FloatToFraction(float Decimal) {
Numerator = Decimal==0.0 ? "0" : string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = Decimal==0.0 ? "0" : string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")" }

ResizeWO = true
OutDAR = 0.0
CropDAR = 0.0
assert(!ResizeWO || OutDAR==0.0 || CropDAR==0.0 || OutDAR>=CropDAR, "blah, blah" + FloatToFraction(CropDAR))

StainlessS
7th April 2019, 02:07
modified the FloatToFraction function instead......

Yeh I nearly suggested that in post #6, guess slipped out of my mind,
Would "0:1" be better result though ?

That mod would be easiest to use now and in future.

hello_hello
7th April 2019, 03:18
Given you got me thinking about it, you could take advantage of the error message always being parsed.

I'm not sure this makes sense as it is (because you could make checking for zero values part of the original Assert conditions), but it works, and you could modify the ZeroCheck function for enforcing other conditions. That sort of thing might come in handy at some stage.
I think it's opened up a whole new can of Assert I hadn't considering until now.

function FloatToFraction(float "Decimal") {
Numerator = !defined(Decimal) || Decimal==0.0 ? "0" : string(ContinuedNumerator(Decimal, Limit=5000))
Denominator = !defined(Decimal) || Decimal==0.0 ? "0" : string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")" }

function ZeroCheck(float "OutDAR", float "CropDAR") {
assert(!defined(OutDAR) || OutDAR != 0.0, "OutDAR can't be zero")
assert(!defined(CropDAR) || CropDAR != 0.0, "CropDAR can't be zero")
return "" }

ResizeWO = true
OutDAR = 0.0
CropDAR = 0.0
assert(!ResizeWO || OutDAR==0.0 || CropDAR==0.0 || OutDAR>=CropDAR, \
ZeroCheck(OutDAR, CropDAR) + "blah, blah" + FloatToFraction(CropDAR))

hello_hello
7th April 2019, 03:19
Yeh I nearly suggested that in post #6, guess slipped out of my mind,
Would "0:1" be better result though ?

I'm not sure. It was just to prevent the error, but yeah.... maybe you're right.

StainlessS
7th April 2019, 06:22
This is something like I would do it.

# Removed Optionality from Decimal arg, If called without a Defined arg then is script error, which should ideally be fixed.
# I dont like hiding errors, makes the buggers difficult to find when you are looking for them, and gives the impression that all is hunky dory, when it aint.
# Direct comparison with 0.0 is usually frowned upon a little, could be eg 0.0000000000000000000000000000000000001, or smaller, would usually
# compare difference from 0.0 with very small number (in C I think its called something like FLOAT_EPSILON in Float.h or Math.h)
# I just looked it up on stackExchange and somebody there reckoned that FLOAT_EPSILON is 1.401298E-45. (Nearest number to 0.0 that is not 0.0).

Function FloatToFraction(Float Decimal) {
NearAsDammitZero = (Abs(Decimal) < 0.000000001) # Prevents error on smaller args
Numerator = NearAsDammitZero ? "0" : string(ContinuedNumerator( Decimal, Limit=5000))
Denominator = NearAsDammitZero ? "1" : string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")"
}

edited:

I'll pass on the ZeroCheck(), you seem determined to hide UnDefined vars (which really should be considered a bug).

EDIT: I've just taken a copy of your above function, Maybe I find use for it, thanx.

hello_hello
7th April 2019, 10:46
# Removed Optionality from Decimal arg, If called without a Defined arg then is script error, which should ideally be fixed.
# I dont like hiding errors, makes the buggers difficult to find when you are looking for them, and gives the impression that all is hunky dory, when it aint.

Yeah, but doesn't defined() only work with function arguments (or when a variable has been explicitly defined or undefined somewhere in the function)?

ie defined(xx) would return true or false if xx is a function argument, but it'd result in an "I don't know what xx means" error otherwise (unless xx had been defined by the function).

I didn't see it as a way of hiding errors. In my case the arguments would always be defined anyway, and I just added the defined check while messing about so an undefined argument wouldn't cause an error, but if you don't set a default value for an argument it'll be undefined by default, and that might be a perfectly valid state.

# Direct comparison with 0.0 is usually frowned upon a little, could be eg 0.0000000000000000000000000000000000001, or smaller, would usually
# compare difference from 0.0 with very small number (in C I think its called something like FLOAT_EPSILON in Float.h or Math.h)
# I just looked it up on stackExchange and somebody there reckoned that FLOAT_EPSILON is 1.401298E-45. (Nearest number to 0.0 that is not 0.0).

I didn't know that, although in my case 0.0 is the default value for the aspect ratio arguments, so if they have a 0.0 value it means they weren't specified when using the function in a script. ie

CropDAR = default(CropDAR, 0.0)

The reason for that is because later in the function there's

assert(IsReallyFloat(CropDAR) && !(CropDAR < 0.0), \
"CropDAR must be floating point (4.0/3.0 or 1.7778 etc)"

So without CropDAR being 0.0 by default, the IsReallyFloat function would need to be modified to accept an undefined argument and ignore it, or Assert would have to begin with a !defined(CropDAR) condition to prevent the next two conditions throwing unnecessary errors, and later in the script when a CropDAR needs to be applied, I'd have to first check if it's defined anyway, so I'd do something like
IsCropDAR = defined(CropDAR)
so instead of using the first line in multiple places, I've been using the second.

defined(CropDAR) ? then : else
IsCropDAR ? then : else

For this function though, it's

IsCropDAR = (CropDAR > 0.0)

I'll confess I don't quite follow the "undefined errors being a bug" theory. Aren't they only a bug when a variable must be defined?

StainlessS
7th April 2019, 14:42
Actually below is about as good as you will get, tested working.
If the guard number has one more 0 decimal place then very small number (eg 0.0) cause the error seen previously (ContinuedFraction: Float Value Out Of Range for Rational Pair)
Below Guard, ie 0.000000001, prevents that error, however FloatToFraction will only produce result greater than 0:1 (it does actually return that) if argument is at least 0.001.
[EDIT: 0.001, That might be related to LIMIT=5000, dont know, never really looked at what ContinuedFraction does]
You can see for yourself by changing arg to eg 0.0 and 0.001 and modding the guard number. You can make it produce eg 0:X when NearAsDammitZero is true, so you can see when we have limited it,
and also when it produces 0:1 then that is from ContinuedFraction and not due to NearAsDammitZero. Change NearAsDammitZero back to 0:1 when finished playing with it, and restore guard to 0.000000001 if your changed it.
(If you change guard to 0.001, then will make no difference to result, its just that we would have limited it instead of ContinuedFraction)
[EDIT: At a guess, I'm thinking that maybe ContinuedFraction limits to 5000:1 and 1:5000, so one more decimal digit in 0.001 ie 0.0001 would be 1/10000 and so out of limit]

Function FloatToFraction(Float Decimal) {
NearAsDammitZero = (Abs(Decimal) < 0.000000001) # Prevents error on smaller args
Numerator = NearAsDammitZero ? "0" : string(ContinuedNumerator( Decimal, Limit=5000))
Denominator = NearAsDammitZero ? "1" : string(ContinuedDenominator(Decimal, Limit=5000))
return "(" + Numerator + ":" + Denominator + ")"
}

BlankClip
x=0.001
s=FloatToFraction(x)
Return Subtitle(s)


VarExist

VarExist(name) AVS+
Tests if the variable exists or not. Note: if variable exists, it returns true regardless of the "defined" state of the variable
http://avisynth.nl/index.php/Internal_functions

Or RT_VarExist(), avs v2.58+.

I think (but have not checked) that IsReallyFloat() is basically (!IsInt && IsFloat), IsFloat really just means Is A Number.

You generally only handle Undefined variables when not using them yourself and just passing on to some other function, where you rely on them to default.
eg
Function MySub(clip c,string text,int "Align") {
return c.Subtitle(text,Align=Align) # Optional arg Align is not used by us, we let subtitle use its default if not defined by caller.
}

I'm not saying that you should never screw with undefined variables, but you really have to know what you are doing and have good reason, not just because you were a bit bored at the time.
You usually only do that when forced to because of some existing problem.

Maybe you should have some kind of DarIsValid flag or something.

hello_hello
7th April 2019, 14:49
I should say thanks, because I'd forgotten about the Abs() function.

I've been doing some rounding/matching in a few places in the script as floating point can only be relied on to be accurate to about four decimal places. Most of the time it doesn't matter, but occasionally the script would do something I'd know it didn't need to, such as cropping with the resizer instead of Crop() because it calculated the required cropping to be 1.99999384554 pixels instead of 2.

I'd written mini essays to work around it, but I think this'll do the trick.

CropLeft = abs(round(CropLeft) - CropLeft)) < 0.0001 ? round(CropLeft) : CropLeft

hello_hello
7th April 2019, 15:11
Or RT_VarExist(), avs v2.58+.

I think (but have not checked) that IsReallyFloat() is basically (!IsInt && IsFloat), IsFloat really just means Is A Number.

You generally only handle Undefined variables when not using them yourself and just passing on to some other function, where you rely on them to default.
eg
Function MySub(clip c,string text,int "Align") {
return c.Subtitle(text,Align=Align) # Optional arg Align is not used by us, we let subtitle use its default if not defined by caller.
}

Maybe you should have some kind of DarIsValid flag or something.

Sorry, I didn't see your last post till I posted, so I'm catching up.

(!IsInt && IsFloat) Yes, that's what IsReallyFloat() is.

IsInt() only returns true for integers, so if IsInt()==false is true (if !IsInt() is true) the variable can't be an integer.
IsFloat() returns true for integers and float, but if it's not an integer, for IsFloat() to also be true, the variable must be float.

Where can I find RT_VarExist()?

I'm still not sure if I'm doing anything odd. If an argument can be left undefined when a function is used in a script, then checking for definedeness in the function doesn't seem like something that can be avoided, unless the function itself defines it otherwise, but that's not always applicable. Anyway... I have to do some real-world stuff for a bit. I should be doing it half an hour ago. I'll return in a while to re-read the rest of your post again though... and try to digest it.

Cheers.

hello_hello
7th April 2019, 15:36
Maybe you should have some kind of DarIsValid flag or something.

Isn't that what defined(AR) and IsReallyFloat(AR) do? ;)

Damn.... gotta run.....

StainlessS
7th April 2019, 15:51
Sorry, bum steer for your use[I've been at the keyboard for about 24 hours, I is knackered.], RT_VarExist would return true for Undefined Function args (they exist but are not defined to be anything).
Only of use in main level script. RT_VarExist in RT_Stats, pretty much any version.


Function SomeFunc(clip c, Var "DAR") {
DarIsFloat = (DAR.Defined && !DAR.IsInt && DAR.IsFloat) # Is Defined, and a Number, and not an Int, so is defo a Float.
DarWasInt = (DAR.Defined && DAR.IsInt) # Is Defined and supplied as Int (is this a user error to flag).
DarWasDefined = (DAR.Defined) # Was Defined on enty.
DAR = DefaulT(DAR,1.0) # Anything but 0.0 or Undefined, Let 1:1 handling code path make the decisions.
}

hello_hello
7th April 2019, 22:16
Could I enquire as to why using 0.0 as the default is bad? All that happens is the function checks, and if the DAR isn't 0.0, it's been specified by the user. A user would never specify a DAR of zero, whereas 1.0 is a possibility.

It's helped me catch a couple of instances in the script where I'd forgotten to check if an AR was defined before including it in the calculations. As soon as something is multiplied or divided by 0.0 you know about it, whereas you can multiply and divide by one all day long.....

For specifying an output width and height, zero as a pseudo "undefined" was virtually mandatory, as I wanted the resizing and cropping to be specified as it is for an Avisynth resizer... CropResize(640,480, 0,6,-8,-4)... while at the same time not making specifying the width and height necessary to avoid typing the argument names for cropping.... CropResize(0,0, 0,6,-8,-4).

Function MySub(clip c,string text,int "Align") { return c.Subtitle(text,Align=Align) # Optional arg Align is not used by us, we let subtitle use its default if not defined by caller. }

Yeah..... but... but... yeah.... but... in order for the subtitle function to use a default value for Align, wouldn't it first have to check if it's been defined..... much like I'm doing??

Even xxx = default(xxx, something)
is really just Avisynth shorthand for xxx = defined(xxx) ? xxx : something
Without default() the default for an argument is undefined anyway, but I'm still not sure I see why zero is automatically a bad choice.

StainlessS
7th March 2020, 21:06
Sorry HH, did not answer this.
Could I enquire as to why using 0.0 as the default is bad?

Well IIRC, mpeg1 aspect ratio flagged as 1:1 just means use the (FAR) Frame Aspect Ratio
[Not sure, but might be H/W rather than W/H for mpeg1]

I guess that 0.0 is not bad, just not what I would use.

EDIT:
DGIndex version of MPEG1 PAR (y/x)
See here:- https://forum.doom9.org/showthread.php?p=1760576#post1760576

hello_hello
8th March 2020, 04:48
I guess it was mostly because I needed the user to be able specify something for the width and height that would be considered undefined, and zero was the choice there, so for consistency I did the same for aspect ratios.

The script checks to make sure they're float (unless they're zero) and not a negative like this:

assert((OutDAR == 0) || ((OutDAR > 0) && !IsInt(OutDAR) && IsFloat(OutDAR)), "blah blah")

Thinking about it, I could've done something like this:

assert(!defined(OutDAR) || ((OutDAR > 0) && !IsInt(OutDAR) && IsFloat(OutDAR)), "blah blah")

but either way somewhere else in the script would be this, and IsOutDAR would determine the calculation to use.

IsOutDAR = (OutDAR > 0)
or it'd be
IsOutDAR = defined(OutDAR)

When the script needs to set an output aspect ratio, it checks whether an OutDAR or OutSAR were specified (greater than zero), and also whether the output is actually anamorphic, and if they're all false:

global MeGUI_darx = !IsAnamorphicOutput ? Undefined() : Something
global MeGUI_dary = !IsAnamorphicOutput ? Undefined() : Something

So 0 or 0.0 aren't used as aspect ratios anywhere. Only as an alternative for undefined().
It really just means the user can undefine an aspect ratio with OutDAR=0 etc if they want to, which is handy when using the global function. If the global function is specifying an InDAR for all further instances of CropResize to use, a single instance of CropResize can over-ride it, or undefine it with zero. ie

GCropResize(640,480, InDAR=4.0/3.0)

A.Trim(0,999).CropResize() ++ \
B.Trim(0.687).CropResize(InDAR=0) ++ \
A.Trim(1000,0).CropResize()