3

Problem Statement

I'm importing a set of coordinate from an unknown source, which I don't have privilege to look into.

set yMin [lindex [lindex $bbox 0] 1]
puts "yMin: <$yMin>"

It works normally.

yMin: <-6.149999999999999e-02>

The brackets are used to check if there are any spaces or even hidden tabs. However, if yMin is multiplied by any number such as 0.5, it goes wrong.

set Y2 [expr {0.5 * $yMin}]

can't use empty string as operand of "*"
while executing "expr {0.5 * $yMin}"
invoked from within "set Y2 [expr {0.5 * $yMin}]"

Even if only yMin is printed, it still has the empty-operand error.

set Y1 [expr $yMin]
puts "Y1: <$Y1>"

empty expression in expression "" (parsing expression "")
invoked from within "expr $yMin"
invoked from within "set Y1 [expr $yMin]"

But there's an interesting test. It works if curly braces are added!

set Y1 [expr {$yMin}]
puts "Y1: <$Y1>"

Y1: <-0.06149999999999999>


How to reproduce the problem

Thanks to Glenn Jackman (see replies below),

% set bbox {{-4.599999999999999e-02 -6.149999999999999e-02} {8.000000000000002e-02 6.149999999999999e-02}}
{-4.599999999999999e-02 -6.149999999999999e-02} {8.000000000000002e-02 6.149999999999999e-02}
% set yMin [lindex [lindex $bbox 0] 1]
-6.149999999999999e-02
% expr {1 + $yMin}
0.9385

Actually, this seems to be not able to reproduce the problem (this is why I have this post). But it could be a template at least.


Trial and Error

The following code is used to check if it's really empty. It turns out not to be empty.

if {$yMin eq {}} {
    puts "Empty records"
    exit 1 
} else {
    puts "yMin is not empty: <$yMin>
}

yMin is not empty: <-6.149999999999999e-02>

Finally, I tried trim, map and regsub to remove any spaces and tabs but none of them works.

set trim_yMin [string trim $yMin]
puts "trim_yMin: <$trim_yMin>"

set map_yMin [string map {" " ""} $yMin]
puts "map_yMin: <$map_yMin>"

regsub -all {\s} $yMin {} reg_yMin
puts "reg_yMin: <$reg_yMin>"

if {$trim_yMin eq {} || $map_yMin eq {} || $reg_yMin eq {}} {
    puts "Empty records"
    exit 1
} else {
    puts "trim_yMin is not empty: <$reg_yMin>"
    puts "map_yMin is not empty: <$reg_yMin>"
    puts "reg_yMin is not empty: <$reg_yMin>"
}

trim_yMin: <-6.149999999999999e-02>
map_yMin: <-6.149999999999999e-02>
reg_yMin: <-6.149999999999999e-02>
trim_yMin is not empty: <-6.149999999999999e-02>
map_yMin is not empty: <-6.149999999999999e-02>
reg_yMin is not empty: <-6.149999999999999e-02>

Here I only shows the result of regsub. Others' results are the same.

set reg_Y2 [expr {0.5 * $reg_yMin}]
puts "0.5 * reg_yMin: $reg_Y2"

can't use empty string as operand of "*"
while executing "expr {0.5 * $reg_yMin}"
invoked from within "set reg_Y2 [expr {0.5 * $reg_yMin}]"

Could you please help me? I really have no idea about what else I can try. Thanks in advance.


Updates and Replies

To Ergwun:

puts $bbox;
set yMin [lindex [lindex $bbox 0] 1]
puts "yMin: <$yMin>"

{-4.599999999999999e-02 -6.149999999999999e-02} {8.000000000000002e-02 6.149999999999999e-02}
yMin: <-6.149999999999999e-02>

set Y2 [expr {0.5 * $yMin}]
puts $Y2

can't use empty string as operand of "*"
while executing "expr {0.5 * $yMin}"
invoked from within "set Y2 [expr {0.5 * $yMin}]"

To benaja:

puts [tcl::unsupported::representation $yMin]

value is a string with a refcount of 4, object pointer at 0x44ed0910, internal representation 0x450d6120:(nil), string representation "-6.14999999999...."

To Shawn,

puts [tcl::unsupported::representation $yMin]
puts [binary encode hex $yMin]
puts [tcl::unsupported::representation $yMin]
puts [string is double -strict $yMin]
puts [tcl::unsupported::representation $yMin]

value is a string with a refcount of 4, object pointer at 0x44ed6030, internal representation 0x450de4b0:(nil), string representation "-6.1499999999..."

2d362e313439393939393939393939393939652d3032

value is a bytearray with a refcount of 4, object pointer at 0x44ed6030, internal representation 0x44ee0dc0:(nil), string representation "-6.1499999999..."

1

value is a double with a refcount of 4, object pointer at 0x44ed6030, internal representation 0xbfaf7ced916872af:(nil), string representation "-6.1499999999..."

set Y2 [expr {0.5 * -6.149999999999999e-02}]
puts "Y2: $Y2"
set new_Y2 [expr {0.5*-6.149999999999999e-02}]
puts "new_Y2: $new_Y2"

Y2: -0.030749999999999996
new_Y2: -0.030749999999999996

To Schelte Bron,

The error message is changed! Maybe it indicates something?

puts [tcl::unsupported::representation $yMin]
set yMin " $yMin"
puts [tcl::unsupported::representation $yMin]
set Y2 [expr {0.5 * $yMin}]
puts $Y2

value is a string with a refcount of 4, object pointer at 0x44ec28a0, internal representation 0x450be540:(nil), string representation "-6.1499999999..."

value is a string with a refcount of 2, object pointer at 0x446bbc70, internal representation 0x44695fd0:(nil), string representation " -6.149999999..."

can't use non-numeric string as operand of "*"
while executing "expr {0.5 * $yMin}"
invoked from within
"set Y2 [expr {0.5 * $yMin}]"

If the calculation still fails after the value has changed to a double after string is double -strict $yMin, then take the result of binary encode hex $yMin, convert it back to a string (using binary decode hex) and use that in the calculation.

puts "Is yMin double? : [string is double -strict $yMin]"
set binary_yMin [binary encode hex $yMin]
set double_binary_yMin [binary decode hex $binary_yMin]
puts "yMin: $yMin"
puts "Binary of double yMin: $binary_yMin"
puts "Double binary double: $double_binary_yMin"
set Y2 [expr {0.5 * $double_binary_yMin}]
puts $Y2

Is yMin double? : 1
yMin: -6.149999999999999e-02
Binary of double yMin: 2d362e313439393939393939393939393939652d3032
Double binary double: -6.149999999999999e-02
can't use empty string as operand of "*"
while executing "expr {0.5 * $double_binary_yMin}"
invoked from within "set Y2 [expr {0.5 * $double_binary_yMin}]"

What do you get with tcl::mathop::* 0.5 $yMin?

puts [tcl::mathop::* 0.5 $yMin]

can't use empty string as operand of "*"
while executing
"tcl::mathop::* 0.5 $yMin"
invoked from within
"puts [tcl::mathop::* 0.5 $yMin]"


Relevant Posts

  1. Why do Tcler suggest to brace your expressions?
  2. What does it mean -can't use empty string as operand of "*" on tcl-? how to resolve
  3. How to strip whitespace in string in TCL?
  4. Removing space from string after reading it from file using TCL
  5. missing operand at @ in tcl script
  6. Determine type of a variable in Tcl
Zureas
  • 53
  • 5
  • 1
    I can't reproduce your error. It's hard to tell what's wrong without knowing what `bbox` is. Can you show the output you get when you run this: `puts $bbox;set yMin [lindex [lindex $bbox 0] 1];puts "yMin: <$yMin>";set Y2 [expr {0.5 * $yMin}];puts $Y2` – Ergwun Sep 16 '21 at 09:57
  • Thank you @Ergwun ! BBox contains two sets of (x,y) coordinates. It stands for coordinates of some bounding box. I've updated and shown the results in the post. Please take a look, thank you! – Zureas Sep 16 '21 at 10:05
  • @Ergwun, sorry I forget adding the results of `set Y2 [expr {0.5 * $yMin}];puts $Y2`. Now it's added. – Zureas Sep 16 '21 at 10:08
  • And did you run the whole thing as a single script (so there is no chance the `$yMin` variable has changed in between lines)? – Ergwun Sep 16 '21 at 10:12
  • Yes, I did. All those tests are in a row. I don’t think there are some variable changes in between lines. – Zureas Sep 16 '21 at 10:23
  • 1
    Sorry, I can't work out what might cause the `[expr]` to complain that `$yMin` is empty when it prints successfully on the previous line. Weird whitespace in `bbox` can break the extraction of the coordinates and cause other errors (like non-numeric value), but not empty string: https://replit.com/@ergwun/EntireThinDrawing#main.tcl – Ergwun Sep 16 '21 at 10:33
  • I see. Thanks so much for helping me reproducing and solving the problem. I'll talk to our coworkers to figure out what might be going on during the code interpretation. Maybe it has something to do with some kind of preprocessing. – Zureas Sep 16 '21 at 10:39
  • 1
    ```::tcl::unsupported::representation``` can be also useful: https://stackoverflow.com/questions/62449211/how-to-know-the-data-type-of-a-variable-in-tcl – benaja Sep 16 '21 at 11:00
  • Thank you, @benaja. It's very informative. I tried this and it turns out to be a string. `value is a string with a refcount of 4, object pointer at 0x44ed0910, internal representation 0x450d6120:(nil), string representation "-6.14999999999...."` – Zureas Sep 16 '21 at 11:49
  • 1
    Can you include the output of `binary encode hex $yMin`? – Shawn Sep 16 '21 at 14:03
  • Hello @Shawn, thanks! It's `2d362e313439393939393939393939393939652d30323f`. I don't understand what it means... – Zureas Sep 16 '21 at 15:13
  • Hmm. Did you include the question mark in that? That's not part of the code. – Shawn Sep 16 '21 at 15:33
  • Oh yes...I did include that question mark. Let me redo this. – Zureas Sep 16 '21 at 15:40
  • @Shawn. It becomes `2d362e313439393939393939393939393939652d3032`. – Zureas Sep 16 '21 at 15:42
  • Which is what it should be. I was wondering if there some some strange extra bytes in there somehow causing it. – Shawn Sep 16 '21 at 15:46
  • 2
    What does `string is double -strict $yMin` return? And does the `tcl::unsupported::representation` description change from before and after doing that? – Shawn Sep 16 '21 at 15:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237184/discussion-between-kao-ethan-and-shawn). – Zureas Sep 16 '21 at 16:09
  • 2
    I wonder if we're going down the wrong path and the issue isn't with the variable, but with `expr`? What version of TCL are you using? Any chance it's a setup that could have replaced the normal `expr` command with a local custom one? – Shawn Sep 17 '21 at 15:10
  • 1
    Does `expr {0.5 * -6.149999999999999e-02}` work? If not, what about without the spaces? – Shawn Sep 17 '21 at 15:18
  • Hi @Shawn, thanks so much for still trying to help me! Sorry for my late reply. I tested both `expr {0.5 * -6.149999999999999e-02}` and `expr {0.5*-6.149999999999999e-02}` and the only difference is the spaces. Both work fine. They give the same result: `-0.030749999999999996`. They're also added in the post. Yea, maybe it's about the `expr`, but I don't know how to replace it. – Zureas Sep 18 '21 at 05:02

3 Answers3

3

Without having the exact starting point in that bbox variable, we can only really speculate. That value should work:

% set yMin -6.149999999999999e-02
-6.149999999999999e-02
% expr {0.5 * $yMin}
-0.030749999999999996

All the looking at ways that expr may fail won't help if the input data is plain old bogus, and empty strings are never numeric enough to support multiplication. (This isn't Python!) We've got to back up a few steps.

I suspect that the problem is that the bbox variable is sometimes not the shape that you expect, sometimes having a value like this: 1.0 -6.149999999999999e-02 3.0 4.0. In that case, the [lindex [lindex $bbox 0] 1] will produce an empty string (because indexing off the end of a list does that because of the history of the language):

% set bbox {1.0 -6.149999999999999e-02 3.0 4.0}
1.0 -6.149999999999999e-02 3.0 4.0
% puts >[lindex [lindex $bbox 0] 1]<
><

Just using lindex $bbox 1 would work better in this case.

% puts >[lindex $bbox 1]<
>-6.149999999999999e-02<

There's a shorthand for nested lindex:

lindex $boox 0 1

but bounding boxes (at least when they come from Tk) are usually just simple lists of four numeric values, X1 Y1 X2 Y2 so using the nested form on them would be very likely to be both wrong and inefficient.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • Thanks so much! I really appreciate your help. I totally agree with you that we can only speculate what happens if we don't know what exactly "bbox" is. Regarding the bbox, I use puts `$bbox` to get `{-4.599999999999999e-02 -6.149999999999999e-02} {8.000000000000002e-02 6.149999999999999e-02}`. You see there are two curly braces so I guess this is a two-dimensional array (or list?), right? Do you think there's any possibilities that cause problems with such data type? – Zureas Sep 16 '21 at 12:02
2

This is just conjecture here: You indicated that the coordinates come from some obscure source. Maybe the developer created a new object type, but did not implement the conversion procedures correctly. When you print the value, the string representation is used. But when you try to multiply the value it wants to convert the mysterious object type to a double, which doesn't seem to work correctly.

You have been trying to remove white space. But because there is no white space, the value is not modified. But what happens if you would add white space? For example: set yMin " $yMin". That should turn the variable into a pure string (you can check with tcl::unsupported::representation). If you then try to multiply it, the regular Tcl conversion procedures for turning a string into a double would be used. Note that white space around numbers is perfectly acceptable for expr, so you should just be able to multiply the modified $yMin.

Schelte Bron
  • 4,113
  • 1
  • 7
  • 13
  • Thanks! The error message is changed! Maybe it indicates something? It becomes `can't use non-numeric string as operand of "*"`. As for `[tcl::unsupported::representation $yMin]` before and after `set yMin " $yMin"`, they are also changed! Please see my updates in the post for detail. – Zureas Sep 17 '21 at 00:44
  • 1
    Not really. That's just the difference between `expr {0.5 * ""}` and `expr {0.5 * " "}`. I think @Shawn is onto something. If the calculation still fails after the value has changed to a double after `string is double -strict $yMin`, then take the result of `binary encode hex $yMin`, convert it back to a string (using `binary decode hex`) and use that in the calculation. But something is horribly wrong with the values you get in the bbox variable. – Schelte Bron Sep 17 '21 at 07:35
  • I see. Thanks so much. Do you mean something like this? `puts "Is yMin double? : [string is double -strict $yMin]"` `set binary_yMin [binary encode hex $yMin]` `set double_binary_yMin [binary decode hex $binary_yMin]` `puts "yMin: $yMin"` `puts "Binary of double yMin: $binary_yMin"` `puts "Double binary double: $double_binary_yMin"` `set Y2 [expr {0.5 * $double_binary_yMin}]` `puts $Y2` The result is the same...`can't use empty string as operand of "*" while executing "expr {0.5 * $double_binary_yMin}"` I've put this test in the post. Thank you. I really appreciate your help. – Zureas Sep 17 '21 at 08:41
  • 1
    That's weird. I'm starting to wonder if we're barking up the wrong tree suspecting the input data. Maybe the shell or extension that provides the coordinates also installed a new version of the `expr` command that is buggy. Every command other than `expr` produces the expected result. What do you get with `tcl::mathop::* 0.5 $yMin`? – Schelte Bron Sep 17 '21 at 22:33
  • Hello Schelte, thanks for still trying to help me. `puts [tcl::mathop::* 0.5 $yMin]` still produces `can't use empty string as operand of "*"` error message. This is also added in the post. – Zureas Sep 18 '21 at 05:11
1

This is not an answer, it's a comment with formatting

% set bbox {{-4.599999999999999e-02 -6.149999999999999e-02} {8.000000000000002e-02 6.149999999999999e-02}}
{-4.599999999999999e-02 -6.149999999999999e-02} {8.000000000000002e-02 6.149999999999999e-02}
% set yMin [lindex [lindex $bbox 0] 1]
-6.149999999999999e-02
% expr {1 + $yMin}
0.9385

Something else is going on in your code that is altering the value of the variable. Have a look at How to create a Minimal, Reproducible Example

glenn jackman
  • 238,783
  • 38
  • 220
  • 352