Changeset 3878
- Timestamp:
- 05/11/09 01:59:59 (4 years ago)
- File:
-
- 1 edited
-
trunk/core/lib/Foswiki/Prefs/Stack.pm (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/core/lib/Foswiki/Prefs/Stack.pm
r3877 r3878 35 35 * A final hash that maps preferences to the level they were finalized. 36 36 37 This class deals with this stuff and must be used only by Foswiki::Prefs.37 This class deals with this stuff and must be used only by =Foswiki::Prefs= 38 38 39 39 =cut … … 328 328 =begin TML 329 329 330 ---++ Mathematical Considerations 330 #MathStuff 331 ---+ Mathematical Considerations 331 332 332 333 The bitmap is built in an way to meet two properties: … … 336 337 Preference levels 0-7 are in the first byte. 8-15 in the second and so on. If a 337 338 preference is defined in levels 2 and 7, for example, its bitmap will have 338 length 1, even if the stack is in 30th level. This is what "minimal possible339 length"means.339 length 1, even if the stack is in 30th level. 340 This is what _minimal possible length_ means. 340 341 341 342 These two properties implies that the last byte of a bit string is non-zero. 342 343 343 --- 344 ---++ Getting/Setting preferences with at most 8 levels 344 345 345 346 Let's consider the first scenario: at most 8 preference values. This means that … … 349 350 listed property above. 350 351 351 This implies that I can *always* take the logarithm of ord($map). (III) 352 353 The question is: given a bitstring, what is the highest level containing a 1? 352 This implies that I can *always* take the logarithm of =ord($map)=. (III) 353 354 The question is: 355 __given a bitstring, what is the highest level containing a 1?__ 356 354 357 To answer this question let's consider the following mathematical expressions: 355 (log2(X) means "the logarithm of X in base 2") 356 358 (=log2(X)= means _the logarithm of X in base 2_) 359 360 <verbatim> 357 361 log2(1) = 0; 1 == 1 * 2 ** 0; 1 in base 2 is "00000001" (considering one byte) 358 362 log2(2) = 1; 2 == 1 * 2 ** 1; 2 in base 2 is "00000010" (considering one byte) 359 363 log2(4) = 2; 4 == 1 * 2 ** 2; 4 in base 2 is "00000100" (considering one byte) 360 364 log2(8) = 3; 8 == 1 * 2 ** 3; 8 in base 2 is "00001000" (considering one byte) 365 </verbatim> 361 366 362 367 Also notice that: 363 2 ** B <= X < 2 ** (B + 1) implies B <= log2(X) < (B + 1). This implies 364 int(log2(X)) == B for any X in the rage. Some examples: 365 368 369 <verbatim> 370 2 ** B <= X < 2 ** (B + 1) implies B <= log2(X) < (B + 1) 371 </verbatim> 372 373 This implies: 374 375 <verbatim> 376 int(log2(X)) == B, for any X in the above rage. 377 </verbatim> 378 379 Some examples: 380 381 <verbatim> 366 382 int(log2(3)) = log2(2) = 1; 3 in base 2 is "00000011" (considering one byte) 367 383 int(log2(5)) = log2(4) = 2; 5 in base 2 is "00000101" (considering one byte) … … 369 385 int(log2(7)) = log2(4) = 2; 7 in base 2 is "00000111" (considering one byte) 370 386 int(log2(9)) = log2(8) = 3; 9 in base 2 is "00001001" (considering one byte) 387 </verbatim> 371 388 372 389 The position of least significant bit is 0 and the position of the most 373 significant bit is 8, then: int(log2(X))is the position of the390 significant bit is 8, then: =int(log2(X))= is the position of the 374 391 highest-significant bit equal to 1. This always holds. The complete 375 392 mathematical proof is left as an exercise. 376 393 377 Back to the question "given a bitstring, what is the highest level containing a 378 1?", it's clear the answer is: int(log2(X)). 379 380 X is the number corresponding to the bitstring character, so X == ord($map). 381 382 Also, log2(Y) == ln(Y)/ln(2) for any Y real positive. 383 384 Then we have: int(log2(X)) == int( ln( ord($map) ) / ln(2) ). 385 386 Conclusion: considering (III) and at most 8 levels I can figure out in 387 which level a preference is defined with the following O(1) operation: 388 394 Back to the question __what is the highest level containing a 1?__ 395 396 It's clear the answer is: =int(log2(X))=. 397 398 =X= is the number corresponding to the bitstring character, so X = =ord($map)=. 399 400 Also, 401 <verbatim> 402 log2(Y) == ln(Y)/ln(2), for any Y real positive 403 </verbatim> 404 405 Then we have: 406 <verbatim> 407 int(log2(X)) == int( ln( ord($map) ) / ln(2) ) 408 </verbatim> 409 410 *Conclusion*: considering (III) and at most 8 levels I can figure out in 411 which level a preference is defined with the following _O(1)_ operation: 412 413 <verbatim> 389 414 $defLevel = int( ln( ord($map) ) / ln(2) ); 390 391 --- 415 </verbatim> 416 417 ---++ Getting/Setting preferences with arbitrary number of levels 392 418 393 419 But preferences may have far more levels than 8. Now let's consider this 394 general case. We'll reduce it to the "at most 8 levels"case.420 general case. We'll reduce it to the _at most 8 levels_ case. 395 421 396 422 At this point I must consider how perl built-in function =vec= works: 423 <verbatim> 397 424 $a = ''; 398 425 vec($a, 0, 1) = 1; print unpack("b*", $a); # "10000000" … … 400 427 vec($a, 7, 1) = 1; print unpack("b*", $a); # "10100001" 401 428 vec($a, 16, 1) = 1; print unpack("b*", $a); # "1010000100000000100000000" 429 </verbatim> 402 430 403 431 The least significant bit is the bit 0 of the first byte. The most significant 404 bit is the bit 7 of the last byte. unpack with "b*"gives us this432 bit is the bit 7 of the last byte. =unpack= with ="b*"= gives us this 405 433 representation, that is different from the one we're used to, but it's only a 406 434 representation. Test for yourself: 407 435 436 <verbatim> 408 437 $a = ''; 409 438 vec($a, 0, 1) = 1; print ord($a); # 1 410 439 vec($a, 2, 1) = 1; print ord($a); # 5 411 440 vec($a, 7, 1) = 1; print ord($a); # 133 412 413 Since ord() operates with one character (or the first one, if length($a) > 1), 414 we have to figure out a way to deal with preferences bigger than 8 levels. 441 </verbatim> 442 443 Since =ord()= operates with one character (or with the first one, if 444 =length($a) > 1=), we have to figure out a way to deal with preferences bigger 445 than 8 levels. 415 446 416 447 The level to consider in order to get a preference value is the highest in … … 420 451 421 452 Since (IV) holds, we can reduce the general case to the restricted case as 422 follows: we calculate the level considering the last byte. We'll get L in 423 [0,7]. Then we transform this value to the correct, considering that the bit 0 424 of the last byte is the bit (N - 1) * 8 of the general string, where N is the 425 total number of bytes. Examples: 426 453 follows: we calculate the level considering the last byte. We'll get =$L= in 454 =[0,7]=. Then we transform this value to the correct, considering that the bit 455 0 of the last byte is the bit =(N - 1) * 8= of the general string, where =N= is 456 the total number of bytes. Examples: 457 458 <verbatim> 427 459 1 byte: bit 0 of the last byte is bit (1 - 1) * 8 == 0 of the string 428 460 2 bytes: bit 0 of the last byte is bit (2 - 1) * 8 == 8 of the string 429 461 3 bytes: bit 0 of the last byte is bit (3 - 1) * 8 == 16 of the string 462 </verbatim> 463 430 464 and so on. 431 465 432 So, considering the general case where $map has arbitrary length, the answer 433 to "what is the highest level containing a 1?" is: 434 466 So, considering the general case where =$map= has arbitrary length, the 467 *general answer* to __what is the highest level containing a 1?__ is: 468 469 <verbatim> 435 470 $defLevel = int( log( ord( substr($map, -1) ) ) / ln(2) ) + 436 471 (length($map) - 1) * 8; 437 438 =substr($map, -1)= is "the last byte of $map" and because of (I) it's non-zero, 439 so log( ord( substr($map,-1) ) ) exists. Because of (II), length($map) is at 440 least 1. So this general expression is *always* valid. 441 442 --- 472 </verbatim> 473 474 =substr($map, -1)= is _the last byte of =$map=_ and because of (I) it's 475 non-zero, so =log( ord( substr($map,-1) ) )= exists. Because of (II), 476 =length($map)= is at least 1. So this general expression is *always* valid. 477 478 ---++ Growth/Shrink operations with at most 8 levels 443 479 444 480 There are growth and shrink operations on the stack and hence in the bitmaps. 445 481 These operations *must* keep (I) and (II). Let's consider the initial case: 446 $stack->{map}is an empty hashref, so both (I) and (II) holds.482 =$stack->{map}= is an empty hashref, so both (I) and (II) holds. 447 483 448 484 The addition of a preference uses =vec()=, that expands the string as (and only … … 455 491 456 492 Considering at most 8 bytes, let's assume we want to restore to the level 5. 457 Notice that: 2 ** (5 + 1) == 64 == "01000000". 64 - 1 == 63 == "00111111". Bits 458 0-5 are 1 and all others are 0. Since (1 & X) == X and (0 & X) == 0, we can 459 build a mask using this process and apply it to the map and we'll get the 460 bitmap restored to the desired level. So, in order to restore to level L we 461 build a mask as ((2 ** (L+1)) - 1) and perform: 462 463 $map &= $mask; 493 Notice that: 494 495 <verbatim> 496 2 ** (5 + 1) == 64 == "01000000" 497 64 - 1 == 63 == "00111111" 498 </verbatim> 499 500 Bits 0-5 are 1 and all others are 0. 501 502 And Since: 503 504 <verbatim> 505 (1 & X) == X 506 (0 & X) == 0 507 </verbatim> 508 509 we can build a mask using this process and apply it to the map and we'll get 510 the bitmap restored to the desired level. So, in order to restore to level =$L= 511 we build a mask as =((2 ** (L+1)) - 1)= and perform: 512 513 <verbatim> 514 $map &= $mask; 515 </verbatim> 464 516 465 517 If the result is 0, we need to remove that preference from the hash, so both 466 518 (I) and (II) holds. 467 519 468 Now considering the general case: if we want to restore to level L, we need to 469 build a mask whose bits 0-L are 1. This mask will have length int($L/8) + 1: 470 520 ---++ Growth/Shrink operations with arbitrary number of levels 521 522 Now considering the general case: if we want to restore to level =$L=, we need 523 to build a mask whose bits 0-L are 1. This mask will have =int($L/8) + 1= 524 bytes. 525 526 <verbatim> 471 527 0 <= $L < 8 implies the mask 1-byte long. 472 528 8 <= $L < 16 implies the mask 2-bytes long. 473 529 16 <= $L < 32 implies the mask 3-bytes long. 530 </verbatim> 474 531 475 532 and so on. We conclude that all bytes of the mask, except the last, will be 476 \xFF (all bits 1). If we map $Lto [0,7], then we have the restricted case533 =\xFF= (all bits 1). If we map =$L= to [0,7], then we have the restricted case 477 534 above. 478 535 479 The number of bytes except the last in the bitstring is int($L/8). The bit 480 position of $L in the last byte is ($L % 8): 481 536 The number of bytes except the last in the bitstring is =int($L/8)=. The bit 537 position of =$L= in the last byte is =($L % 8)=: 538 539 <verbatim> 482 540 Level 8 corresponds to bit 0 of the second byte. int(8/8) = 1. 8 % 8 = 0. 483 541 Level 9 corresponds to bit 1 of the second byte. int(9/8) = 1. 9 % 8 = 1. 484 542 Level 15 corresponds to bit 7 of the second byte. int(15/8) = 1. 15 % 8 = 7. 485 543 Level 16 corresponds to bit 0 of the third byte. int(16/8) = 2. 16 % 8 = 0. 544 </verbatim> 486 545 487 546 So the general way to build the mask is: 488 547 548 <verbatim> 489 549 $mask = ("\xFF" x int($L/8)); # All bytes except the last have all bits 1. 490 550 $mask .= chr( ( 2**( ( $L % 8 ) + 1 ) ) - 1 ); # The last byte is built based 491 551 # on the restricted case above. 492 493 The $mask has the minimal possible length, cause the way it's built. $map & 494 $mask has at most length($mask) bytes, cause the way & works. But we still must 495 guarantee (I) and (II), so we need to purge the possible zero-bytes in the end 496 of the bitstring: 497 552 </verbatim> 553 554 The =$mask= has the minimal possible length, cause the way it's built. 555 =$map & $mask= has at most =length($mask)= bytes, cause the way =&= works. But 556 we still must guarantee (I) and (II), so we need to purge the possible 557 zero-bytes in the end of the bitstring: 558 559 <verbatim> 498 560 while (ord(substr($map, -1)) == 0 && length($map) > 0 ) { 499 561 substr($map, -1) = ''; 500 562 } 501 502 We need to test if length($map) is greater than 0, otherwise we may enter on an 503 infinite loop, if all bytes of the result are 0. 504 505 This =while= guarantee (I) above. Then we check if the resulting $map has 506 length 0. If so we remove the pref from the hash, so (II) is achieved. 507 508 ---++ Other Considerations 509 510 This implementation is more complex than the "natural" way, but using this we 511 avoid to have more than one copy of preference values and this architecture 512 (index separated from the values) make it easy to change the way values are 513 stored. And consider it's slow to copy large chunks of data around. All copied 514 values in this architecture are far smaller than the preferences values (a 515 typical big bitstring has less than 4 bytes, while a preference value is bigger 516 than this). 563 </verbatim> 564 565 We need to test if =length($map)= is greater than 0, otherwise we may enter on 566 an infinite loop, if all bytes of the result are 0. 567 568 This =while= guarantee (I) above. Then we check if the resulting =$map= has 569 length 0. If so we remove the pref from the hash, so (II) is also achieved. 570 571 ---+ Other Considerations 572 573 This implementation is more complex than the "natural" way, but using this: 574 * We avoid to have more than one copy of preference values 575 * This architecture (index separated from the values) make it easy to change 576 the way values are stored. 577 578 Also, consider it's slow to copy large chunks of data around. All copied values 579 in this architecture are far smaller than the preferences values (a typical big 580 bitstring has less than 4 bytes, while a preference value is bigger than this). 517 581 518 582 =pack= and =unpack= are not used cause they are not needed and cause the way to
Note: See TracChangeset
for help on using the changeset viewer.
